Create React Gantt Chart Component with dhtmlxGantt

| Comments (20)

Updated on May 28, 2019

We won’t stop till show you that you can use our Gantt chart library with any technology! And today is the turn of ReactJS, popular JavaScript component-based library. Following the steps of this tutorial, you’ll know how to create React Gantt chart app with some basic features. So, let’s start.

As always, you can find React gantt chart component demo on GitHub.

Creating a Simple React App

The very first thing we need to do is to initialize the application structure. For this, we are going to use Create React app tool. It can be installed with the following command:

yarn global add create-react-app

You can find some additional information in this article.

Then we create an app:

create-react-app gantt-react

When our app is created, we go to the folder and run it for the first time:

cd gantt-react
yarn start

The first steps are completed, and as a result, our app should be started on http://localhost:3000/

gantt chart react

Now we need to get dhtmlxGantt code:

yarn add dhtmlx-gantt

Style agreement

In this tutorial we’ll use the following folder structure:
– Each component will be placed inside a separate folder, named after the component.
– Each folder will contain index.js file, which will explicitly specify which classes are exported by the component, and a file with the component implementation.
For example, when we create our Gantt component, it will have the following structure:

./src/components/Gantt/
|- Gantt.js
|- index.js
|- Gantt.css

Gantt.js will contain the implementation of the component, and index.js will export it:

import Gantt from './Gantt';
import './Gantt.css';
export default Gantt;

And the outer code will import the folder

import { Gantt } from "./components/Gantt";

That way if we add extra components to our Gantt, they will be defined under the same folder and it won’t affect imports and won’t clutter our folder structure.
Now, when we’ve sorted it out, let’s proceed.

Let’s start with our Gantt component.
The first thing you need is to add dhtmlxGantt package to your project.
A free version of it can be added via npm or yarn:

yarn add dhtmlx-gantt

Then, create src/components/Gantt folder.
Here we’ll add a React Component wrapper for dhtmlxGantt.
Create Gantt.js file and open it:
src/components/Gantt/Gantt.js:

import React, { Component } from 'react';
import { gantt } from 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
 
export default class Gantt extends Component {
    componentDidMount() {
        gantt.config.xml_date = "%Y-%m-%d %H:%i";
        const { tasks } = this.props;
        gantt.init(this.ganttContainer);
        gantt.parse(tasks);
    }

    render() {
       return (
           <div
                ref={ (input) => { this.ganttContainer = input } }
                style={ { width: '100%', height: '100%' } }
            ></div>
       );
    }
}

And create an index.js file with the following content:

import Gantt from './Gantt';
export default Gantt;

What we have done here – we’ve created a react component that currently serves as a wrapper for dhtmlxGantt js library. dhtmlxGantt itself is a regular JS library that lives outside ReactJS world, thus we created a wrapper component.
Once our component is mounted, we initialize dhtmlxGantt and attach it to DOM. We can also populate it with the data passed via props.

Note, that since a free version of dhtmlxGantt has no destructor, we do not define componentWillUnmount. That also means that if we remove a component from react at some point, the instance of dhtmlxGantt will stay in memory and will be reused next time when the component is mounted again.

We have also specified xml_date config, which sets the format of dates that will come from the data source so that Gantt could parse them correctly.

Now let’s add Gantt to our App component. Note that we use hard-coded data for this sample:
./src/App.js:

import React, { Component } from 'react';
import Gantt from './components/Gantt';
import './App.css';

const data = {
    data: [
        { id: 1, text: 'Task #1', start_date: '15-04-2019', duration: 3, progress: 0.6 },
        { id: 2, text: 'Task #2', start_date: '18-04-2019', duration: 3, progress: 0.4 }
    ],
    links: [
        { id: 1, source: 1, target: 2, type: '0' }
    ]
};
class App extends Component {
    render() {
     return (
        <div>
             <div className="gantt-container">
            <Gantt tasks={data}/>
             </div>
        </div>
     );
    }
 }
 export default App;

If we run the app now, we should see gantt chart with initial tasks on a page:

yarn start

react gantt chart

Configuring Gantt chart

Let’s explore the API of the component a bit more. If you implement an app with the Gantt chart, at some moment the client will ask you to add the ability to zoom the time scale of the Gantt chart.

How it’s usually done:

  • the time scale of dhtmlxGantt is defined by a number of settings, such as number of rows, time step of each row, format of labels, etc.
  • these settings can be changed dynamically

So you need to define several configuration presets for different zoom levels (minutes/hours/days/months/…) and give a user some kind of UI, usually a toggle or a slider, to switch between them.

Let’s try to implement it in React. Firstly, let’s go to the Gantt component and implement a couple of presets for time scale configuration.

Open Gantt.js. to add the following function to it:

    setZoom(value) {
        switch (value) {
            case 'Hours':
                gantt.config.scale_unit = 'day';
                gantt.config.date_scale = '%d %M';

                gantt.config.scale_height = 60;
                gantt.config.min_column_width = 30;
                gantt.config.subscales = [
                    { unit:'hour', step:1, date:'%H' }
                ];
            break;
            case 'Days':
                gantt.config.min_column_width = 70;
                gantt.config.scale_unit = 'week';
                gantt.config.date_scale = '#%W';
                gantt.config.subscales = [
                    { unit: 'day', step: 1, date: '%d %M' }
                ];
                gantt.config.scale_height = 60;
            break;
            case 'Months':
                gantt.config.min_column_width = 70;
                gantt.config.scale_unit = 'month';
                gantt.config.date_scale = '%F';
                gantt.config.scale_height = 60;
                gantt.config.subscales = [
                    { unit:'week', step:1, date:'#%W' }
                ];
            break;
            default:
            break;
        }
    }

    shouldComponentUpdate(nextProps) {
        return this.props.zoom !== nextProps.zoom;
    }

    componentDidUpdate() {
        gantt.render();
    }

Pay your attention to a couple of important things here. When using dhtmlxGantt, it’s expected that the chart will be mounted to DOM using gantt.init, and all further repaints will be called using gantt.render.

Thus, in addition to Component.render which will handle the initial render, we defined componentDidUpdate handler which will repaint gantt on updates.

And since repainting of gantt is quite a costly procedure, we’ll make sure that it’ll be called only when it’s needed by checking whether props have actually changed in shouldComponentUpdate.

Also add a call to render function beginning:

 render() {
   const { zoom } = this.props;
   this.setZoom(zoom);
   return (
     <div
       ref={(input) => { this.ganttContainer = input }}
       style={{ width: '100%', height: '100%' }}
     ></div>
   );
 }

Now Gantt chart scales should be defined by a “zoom” property. If zoom property is changed we should call render function to redraw gantt with new scales.

Now, let’s add UI for selecting a zoom level. We’ll go with a simple toolbar and toggles.

Create the component Toolbar:

src/components/Toolbar/index.js:

import Toolbar from './Toolbar';
export default Toolbar;

src/components/Toolbar/Toolbar.js:

import React, { Component } from 'react';
export default class Toolbar extends Component {
    handleZoomChange = (e) => {
        if (this.props.onZoomChange) {
            this.props.onZoomChange(e.target.value)
        }
    }
    render() {
        const zoomRadios = ['Hours', 'Days', 'Months'].map((value) => {
            const isActive = this.props.zoom === value;
            return (
                <label key={ value } className={ `radio-label ${isActive ? 'radio-label-active': ''}` }>
                    <input type='radio'
                        checked={ isActive }
                        onChange={ this.handleZoomChange }
                        value={ value }/>
                    { value }
                </label>
            );
        });

        return (
            <div className="tool-bar">
                <b>Zooming: </b>
                    { zoomRadios }
            </div>
        );
    }
}

As you can see it’s pretty straightforward – we add a group of radio buttons and provide onZoomChange handler for a parent component.

Here we add a toolbar to the App component:

import Toolbar from './components/Toolbar';

and a handler for change event:

  state = {
        currentZoom: 'Days'
    };
 
    handleZoomChange = (zoom) => {
        this.setState({
            currentZoom: zoom
        });
    }

JSX:

render() {
        const { currentZoom } = this.state;
        return (
            <div>
                <div className="zoom-bar">
                    <Toolbar
                        zoom={currentZoom}
                        onZoomChange={this.handleZoomChange}
                    />
                </div>
                <div className="gantt-container">
                    <Gantt
                        tasks={data}
                        zoom={currentZoom}
                    />
                </div>
            </div>
        );
    }

Now, each time a user selects zoom level in a toolbar, changes will be captured by the App, which will then pass an updated state to Gantt.

react gantt

Processing Changes Made in Gantt

What else would you need while adding a Gantt chart to your app? Most probably you’ll need to do something with changes that users make in a Gantt chart – send changes to the backend or update other components.

In this tutorial we won’t cover saving changes to the database, instead, we’ll show how to capture these changes, and then pass them somewhere in the app.

You can capture Gantt changes using the special dataProcessor module, embedded into dhtmlxGantt. It can serve as a single point for tracking user actions inside Gantt.

Here is how it can be used. Open src/components/Gantt/Gantt.js and add the following method:

   initGanttDataProcessor() {
        const onDataUpdated = this.props.onDataUpdated;
        this.dataProcessor = gantt.createDataProcessor((entityType, action, item, id) => {
            return new Promise((resolve, reject) => {
                if (onDataUpdated) {
                    onDataUpdated(entityType, action, item, id);
                }
                return resolve();
            });
        });
    }
    componentWillUnmount() {
        if (this.dataProcessor) {
            this.dataProcessor.destructor();
            this.dataProcessor = null;
        }
    }

That’s how we can capture all changes made in Gantt and send them to the parent component.

Note that we also save the dataProcessor instance returned from createDataProcessor and clean it up in componentWillUnmount.

Now update App component – what we want to do here is to simply catch events, create descriptive messages for them and put those messages into the local state:

    state = {
        currentZoom: 'Days',
        messages: [],
    };
    addMessage(message) {
        const maxLogLength = 5;
        const newMessate = { message };
        const messages = [
            newMessate,
            ...this.state.messages
        ];

        if (messages.length > maxLogLength) {
            messages.length = maxLogLength;
        }
        this.setState({ messages });
    }

    logDataUpdate = (entityType, action, itemData, id) => {
        let text = itemData && itemData.text ? ` (${itemData.text})`: '';
        let message = `${entityType} ${action}: ${id} ${text}`;
        if (entityType === 'link' && action !== 'delete' ) {
            message += ` ( source: ${itemData.source}, target: ${itemData.target} )`;
        }
        this.addMessage(message);
    }

After that, create a component that will display these messages on the page:

import React, { Component } from 'react';

export default class MessageArea extends Component {
    render() {
        const messages = this.props.messages.map(({ message }) => {
            return <li key={ Math.random() }>{message}</li>
        });

        return (
            <div className="message-area">
                <h3>Messages:</h3>
                <ul>
                    { messages }
                </ul>
            </div>
        );
    }
}

MessageArea.defaultProps = {
    messages: []
};

And finally connect this component to the App:

imports:

import MessageArea from './components/MessageArea';

JSX:

render() {  
     const { currentZoom, messages } = this.state;
        return (
         <div>
            <Toolbar
                zoom={ currentZoom }
                onZoomChange={ this.handleZoomChange }
            />
            <div className="gantt-container">
                <Gantt
                    data={ data }
                    zoom={ currentZoom }
                    onDataUpdated ={ this.logDataUpdate }
                />
            </div>
            <MessageArea
                messages={ messages }
            />
         </div>
}

How everything works now – each time a user changes something in Gantt, we call promise handler in the App component and update MessageArea, which prints action details on the page. It’s exactly what we wanted.

If we run the app now and change some tasks or links, we should see appropriate messages under the Gantt chart.

reactjs gantt chart

Download React gantt chart component demo from GitHub

Conclusion

Today we have shown you how to add a Gantt chart to ReactJS app, how to configure the Gantt chart and to process changes made by the user. As you can see, dhtmlxGantt allows doing it quite easily.

What would you like to see in our next articles about dhtmlxGantt?

We need your feedback to provide the right integrations at the right time. Please leave your requests here:

Thank you in advance and stay tuned for new tutorials!

Comments

  1. nancy mayers June 6, 2017 at 7:25 pm

    Hello! Thanks for these integration guides about Gantt and different frameworks. Do you plan to make similar tutorials for dhtmlxScheduler?

    • Aras Kairys (DHTMLX team) June 7, 2017 at 5:12 pm

      Hi Nancy,
      Thanks for your feedback.
      Yes, we do plan to write similar guides for dhtmlxScheduler too, but unfortunately, we can’t say for sure when they will be ready.

      • Kashif April 16, 2018 at 3:03 pm

        Another request for dhtmlxScheduler integration with React. I shall be grateful.

        • Aras Kairys (DHTMLX team) April 18, 2018 at 4:11 pm

          Hi Kashif,
          Yes, we have the guide in plans, but still can’t say for sure when it will be ready.

  2. Rohadma kumar June 9, 2017 at 5:58 pm

    Hi team,
    I’ve followed this guide, but still getting errors. Can you please help?

    • Aras Kairys (DHTMLX team) June 12, 2017 at 1:34 pm

      Hi Rohadma,
      Can you please provide us more details about the issue?
      Please contact me via support@dhtmlx.com or create a topic on our forum: https://forum.dhtmlx.com

      • Anshul August 17, 2017 at 3:02 pm

        I am getting this error :

        gantt is undefined

        • Aras Kairys (DHTMLX team) August 17, 2017 at 3:13 pm

          Hi Anshul,
          Please make sure that you’ve set the correct path to dhtmlxGantt library.
          You can follow this guide: https://docs.dhtmlx.com/gantt/desktop__how_to_start.html

          • Anshul August 17, 2017 at 3:30 pm

            Hi Aras,

            Thanks for responding.

            I have installed dhtmlxGantt by npm and imported in Gantt.js as given above

            Still same error

          • Mile October 31, 2017 at 12:18 pm

            Same Error for me

          • Aras Kairys (DHTMLX team) October 31, 2017 at 1:06 pm

            Hi Anshul and Mile,
            Please create a topic on our forum: https://forum.dhtmlx.com/

          • Aras Kairys (DHTMLX team) December 15, 2017 at 5:18 pm

            We’ve just found out that the error may happen if you skip /*global gantt*/ declaration from Gantt.js code. Since Gantt instance is declared in dhtmlxgantt module and we access it via a global variable, we add a declaration-comment to make eslint: https://eslint.org/docs/rules/no-undef know about it and not throw ‘not defined’ error. We’ve updated the tutorial to avoid this problem.

  3. Fede November 21, 2017 at 5:06 pm

    I was able to implement the component in React (with Meteor, React Router) and it was great. However, I have a big problem: every time I leave the gantt and to go to another module and then return, the events of the tasks (add, modify, delete, etc) are doubled or tripled. Any ideas?

    • Aras Kairys (DHTMLX team) November 21, 2017 at 6:34 pm

      Hi Fede,
      Event listeners that are attached on componentDidMount do not disappear when the component is unmounted, so you get a new set of listeners each time you re-mount the component.
      The simplest workaround is to set some flag in order to apply events only once:
      componentDidMount() {

      if(!gantt.$eventsAttached){
      gantt.$eventsAttached = true;
      gantt.attachEvent(‘onAfterTaskAdd’, (id, task) => {
      if(this.props.onTaskUpdated) {
      this.props.onTaskUpdated(id, ‘inserted’, task);
      }
      });

      }

      gantt.init(this.ganttContainer);
      gantt.parse(this.props.tasks);
      }

      • Fede November 21, 2017 at 9:38 pm

        But… if i use a React Property i can’t change the it’s value. If i use a state, it doen’t work becouse it is overwritten. If i use a js variable like
        constructor(props){ this.flag = false } i have the same problem.
        This “gantt.$eventsAttached” is a flag that i should create in some js over the react files? Maybe in a node-modules/dhtmlx-gantt directory?

      • Fede November 21, 2017 at 9:53 pm

        Ohh thanks!!! thanks!!! thanks!!! I’ve lost two days with this issues!! I’m really gratefull to you Aras!!! This component it’s really great!!! thanks a lot!!!!

  4. Arthur Medeiros November 29, 2017 at 11:31 pm

    Hi there,
    I was kind of facing the same issue described by Fede. I was able to solve it with the workaround suggested.
    Now… I have another big issue, I am not using the lightbox to create new tasks. Instead, I open a modal and let the user insert all the data in a form. After clicking on “save and close”, I am saving my new task in the database using Axios.
    After doing that I set a different value to a state variable, so the component should render again, and retrieve data from my database. I was able to do all that, but the component won’t refresh the new data it is receiving.
    Do you think you can help me out?

    • Aras Kairys (DHTMLX team) November 30, 2017 at 7:03 pm

      Hi Arthur,
      Can you please provide us a demo so as we can reproduce the problem?
      Please create a topic on our forum: https://forum.dhtmlx.com/ or use our official support system: http://support.dhtmlx.com/ if you are a licensed user.

  5. Jyrki Kateinen June 26, 2018 at 3:04 am

    This example is wrong in so mant levels that i cant even begin…

    • Liudas Kairys (DHTMLX team) June 26, 2018 at 2:05 pm

      Hi Jyrki,
      Feel free to point out what’s wrong, we’ll do our best to improve it.

Leave a Reply