Create React Gantt Chart Component with dhtmlxGantt

| Comments (4)

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 application structure. For this, we are going to use Create React app tool. It can be installed with following command:

npm install -g 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
npm 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:

npm install dhtmlx-gantt --save

After that we can create react component for gantt chart. Open src folder and create Gantt.js and put following content to it:

/*global gantt*/
import React, { Component } from 'react';
import 'dhtmlx-gantt';
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
 
export default class Gantt extends Component {
  componentDidMount() {
    gantt.init(this.ganttContainer);
    gantt.parse(this.props.tasks);
  }
 
  render() {
    return (
        <div
            ref={(input) => { this.ganttContainer = input }}
            style={{width: '100%', height: '100%'}}
        ></div>
    );
  }
}

What we have done here – we’ve initialized dhx gantt once the component is mounted to DOM and populated it with the data from props.

Note, that since 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.

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

import React, { Component } from 'react';
import Gantt from './Gantt';
import './App.css';
 
var data = {
  data: [
    {id: 1, text: 'Task #1', start_date: '15-04-2017', duration: 3, progress: 0.6},
    {id: 2, text: 'Task #2', start_date: '18-04-2017', 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.

gantt react

Configuring Gantt chart

Let’s explore 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:

this.setZoom(this.props.zoom);

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 Toolbar.js with following code:

import React, { Component } from 'react';
 
export default class Toolbar extends Component {
  constructor(props) {
    super(props);
    this.handleZoomChange = this.handleZoomChange.bind(this);
  }
 
  handleZoomChange(e) {
    if(this.props.onZoomChange){
      this.props.onZoomChange(e.target.value)
    }
  }
 
  render() {
    let zoomRadios = ['Hours', 'Days', 'Months'].map((value) => {
      let 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="zoom-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 './Toolbar';

and a handler for change event:

handleZoomChange(zoom) {
  this.setState({
    currentZoom: zoom
  });
}

JSX:

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

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

react gantt

Processing Changes Made in Gantt

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

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

In order to do so, we’ll capture the related API events of dhtmlxGantt, send them to the parent component, and then show the list of changes somewhere on the page.

Open Gantt.js and add the following code right before gantt initialization:

    gantt.attachEvent('onAfterTaskAdd', (id, task) => {
      if(this.props.onTaskUpdated) {
        this.props.onTaskUpdated(id, 'inserted', task);
      }
    });
 
    gantt.attachEvent('onAfterTaskUpdate', (id, task) => {
      if(this.props.onTaskUpdated) {
        this.props.onTaskUpdated(id, 'updated', task);
      }
    });
 
    gantt.attachEvent('onAfterTaskDelete', (id) => {
      if(this.props.onTaskUpdated) {
        this.props.onTaskUpdated(id, 'deleted');
      }
    });
 
    gantt.attachEvent('onAfterLinkAdd', (id, link) => {
      if(this.props.onLinkUpdated) {
        this.props.onLinkUpdated(id, 'inserted', link);
      }
    });
 
    gantt.attachEvent('onAfterLinkUpdate', (id, link) => {
      if(this.props.onLinkUpdated) {
        this.props.onLinkUpdated(id, 'updated', link);
      }
    });
 
    gantt.attachEvent('onAfterLinkDelete', (id, link) => {
      if(this.props.onLinkUpdated) {
        this.props.onLinkUpdated(id, 'deleted');
      }
    });

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

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:

  constructor(props) {
    super(props);
    this.state = {
      currentZoom: 'Days',
      messages: []
    };
 
    this.handleZoomChange = this.handleZoomChange.bind(this);
    this.logTaskUpdate = this.logTaskUpdate.bind(this);
    this.logLinkUpdate = this.logLinkUpdate.bind(this);
  }
 
  addMessage(message) {
    var messages = this.state.messages.slice();
    var prevKey = messages.length ? messages[0].key: 0;
 
    messages.unshift({key: prevKey + 1, message});
    if(messages.length > 40){
      messages.pop();
    }
    this.setState({messages});
  }
 
  logTaskUpdate(id, mode, task) {
    let text = task && task.text ? ` (${task.text})`: '';
    let message = `Task ${mode}: ${id} ${text}`;
    this.addMessage(message);
  }
 
  logLinkUpdate(id, mode, link) {
    let message = `Link ${mode}: ${id}`;
    if (link) {
      message += ` (source: ${link.source}, target: ${link.target})`;
    }
    this.addMessage(message)
  }

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

import React, { Component } from 'react';
 
class MessageArea extends Component {
  render() {
    let messages = this.props.messages.map(({key, message}) => {
      return <li key={key}>{message}</li>
    });
 
    return (
      <div className="message-area">
        <ul>
          {messages}
        </ul>
      </div>
    );
  }
}
 
MessageArea.defaultProps = {
  messages: []
};
 
export default MessageArea;

And finally connect this component to the App:

imports:

import MessageArea from './MessageArea';

JSX:

    return (
      <div>
        <Toolbar
            zoom={this.state.currentZoom}
            onZoomChange={this.handleZoomChange}
        />
        <div className="gantt-container">
          <Gantt
            tasks={data}
            zoom={this.state.currentZoom}
            onTaskUpdated={this.logTaskUpdate}
            onLinkUpdated={this.logLinkUpdate}
          />
        </div>
        <MessageArea
            messages={this.state.messages}
        />
      </div>
    );

How everything works now – each time user changes something in gantt, we capture that action in the App component and update MessageArea which prints action details on a 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 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.

  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?

Leave a Reply