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.
- Fetching data in child component
- this.forceUpdate inside getData() function, after data is fetched
- getDerivedStateFromProps in both parent and child to trigger a refresh
- UNSAFE_componentWillMount in both parent and child to trigger a refresh
- 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>
);
}
}