[REACT] [Gantt] Chiild component not re-render

Full Disclaimer: Newbie to React

There’s a good chance I bit off more than I can chew, trying to implement Gantt on my first programming project, it seem so intuitive. That is until I tried fetching data, and now I am fully lost at where/how to invoke a re-render of the child component

PROBLEM: My API only returns the data array for the data object. The data array is initial set to empty in my parent state, while the links are initialize with values in the state. In the render function I later combine the data array and links array to create the tasks props to send to child component. Inside the on componentDidMount cycle, we fetch the data with the getData() function and a re-render is triggered, however the data returns too late to trigger a re-render on the child component. The data actually returns from the api after the re-render is triggered, which is too late for the child component. I really haven’t changed much from the original react demo, except for the fetching.

ATTACHMENTS: I’m attaching my chrome console log screenshot, index.js (parent component) file, gantt.js (child component) file. Inside the chrome console, outputs prefix with INS are coming from the index file, and those prefix with GNT is coming fro gantt.js.

ATTEMPTED SOLUTION: As I struggled with this for two days now, I think I have tried everything under the sun.

  1. Fetching data in child component
  2. this.forceUpdate inside getData() function, after data is fetched
  3. getDerivedStateFromProps in both parent and child to trigger a refresh
  4. UNSAFE_componentWillMount in both parent and child to trigger a refresh
  5. a host of others I can’t recall right now

As you can see from the console, the child component renders while the data: [] is initially empty, once the child renders and the parent mounts with componentDidMount, the fetch api is ran and the update state triggers the re-render for the parent only. This works perfectly if I hard code data in the state for data: [], but fails if I try to load from the api. As a side note the data is being fetched from a Postgres db.

CONCLUSION: Thanks in advance for your assistance. I hope this is sufficient information.

CODE:

[index.js]

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

class charts extends Component {
  state = {
    currentZoom: 'Days',
    messages: [],
    data: [],
    links: [
      { id: 1, source: 1, target: 2, type: '0' },
      { id: 2, source: 1, target: 3, type: '1' }
    ]
    /*
    data: {
        data: [
          { id: 1, text: 'Task #1', start_date: '2019-04-15', duration: 3, progress: 0.6 },
          { id: 2, text: 'Task #2', start_date: '2019-04-18', duration: 3, progress: 0.9 },
          { id: 3, text: 'Task #3', start_date: '2019-04-20', duration: 5, progress: 0.1 }
        ],
        links: [
          { id: 1, source: 1, target: 2, type: '0' },
          { id: 2, source: 1, target: 3, type: '1' }
        ]
      },*/
  };

  getData(){
    const url = "http://localhost:3000/gantt" ;
    fetch(url)
      .then(response => response.json())
      .then(data => {
        this.setState({data})
      })
      .catch(err => console.log(err))
  }

  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 = (type, action, item, id) => {
    let text = item && item.text ? ` (${item.text})` : '';
    let message = `${type} ${action}: ${id} ${text}`;
    if (type === 'link' && action !== 'delete') {
      message += ` ( source: ${item.source}, target: ${item.target} )`;
    }
    this.addMessage(message);
  }

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

  componentDidMount(){
    console.log("INS ....PRE this.getData in cDM")
    console.log("STATE", this.state)
    this.getData()
    console.log("INS ....POST this.getData in cDM")
    console.log("STATE", this.state)
  }

  render() {
    const { currentZoom, messages, data, links } = this.state;
    const tasks = {}
    tasks["data"] = data
    tasks["links"] = links
    console.log("INS ....render call index.js")
    console.log("STATE", this.state)
    console.log("tasks", tasks)
    return (
      <div>
        <div className="zoom-bar">
          <Toolbar
            zoom={currentZoom}
            onZoomChange={this.handleZoomChange}
          />
        </div>
        <div className="gantt-container">
          <Gantt
            tasks={tasks}
            data={data}
            links={links}
            zoom={currentZoom}
            onDataUpdated={this.logDataUpdate}
          />
        </div>
        <MessageArea
          messages={messages}
        />
      </div>
    );
  }
}

export default {
    routeProps: {
        path: '/gantt',
        component: charts
    },
    name: '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 {

  // instance of gantt.dataProcessor
  dataProcessor = null;

  /**
   * applies one of the predefined settings of the time scale
   */
  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;
    }
  }

  initGanttDataProcessor() {
    /**
     * type: "task"|"link"
     * action: "create"|"update"|"delete"
     * item: data object object
     */
    const onDataUpdated = this.props.onDataUpdated;
    this.dataProcessor = gantt.createDataProcessor((type, action, item, id) => {
      return new Promise((resolve, reject) => {
        if (onDataUpdated) {
          onDataUpdated(type, action, item, id);
        }

        // if onDataUpdated changes returns a permanent id of the created item, you can return it from here so dhtmlxGantt could apply it
        // resolve({id: databaseId});
        return resolve();
      });
    });
  }

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

  componentDidUpdate() {
    gantt.render();
  }

  componentDidMount() {
    console.log("GNT ....START componentDidMount")
    console.log("PROPS", this.props)
    gantt.config.xml_date = "%Y-%m-%d %H:%i";
    const { tasks } = this.props;
    gantt.init(this.ganttContainer);
    this.initGanttDataProcessor();
    gantt.parse(tasks);
  }

  componentWillUnmount() {
    if (this.dataProcessor) {
      this.dataProcessor.destructor();
      this.dataProcessor = null;
    }
  }

  render() {
    console.log("GNT ....PRE this.setZoom in render")
    console.log("PROPS", this.props)
    const { zoom } = this.props;
    this.setZoom(zoom);
    console.log("GNT ....POST this.setZoom in render")
    console.log("PROPS", this.props)
    return (
      <div
        ref={(input) => { this.ganttContainer = input }}
        style={{ width: '100%', height: '100%' }}
      ></div>
    );

  }
}

Hello Byron,
I don’t specialize in React, so I won’t be able to suggest to you how everything should work in a React-way.
But I can tell you that you need to give Gantt already prepared data, not the data that is going to be updated later. Otherwise, you need to call the gantt.parse(data) again. You can also try loading the data with the gantt.load() command if you do that from the backend.

1 Like

@ramil Thanks for the tips. I will try your suggestion with parse and load

how to resolve the problem that you say

vue components how to get async data and render gantt

Hello Jinhua,
Please, don’t post the same questions in different topics.
It is better to continue discussing your question there:


It would be nice if you provide a detailed description of your requirements.