What is the correct pattern in ReactJS lifecycle (v. 16.4) to display data in child component from componentDidMount in parent component?
I have a scenario which should be simple enough, but it is not behaving the way I expect it to. I want to pass data from a parent component to the child component which in its turn transforms the data into something that can be displayed.
I have read this article https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html, and am trying to get the pattern right.
Previously I used componentWillMount
to do this, and that worked fine, but as I said, it behaves strangely with componentDidMount
.
My parent component gets data in its componentDidMount
which updates state which is then passed to the child component:
class NewTable extends Component {
constructor(props) {
super(props);
this.state = {
dataSet: {}
}
}
componentDidMount() {
const dataSet = DataSet.getColorDataSet();
this.setState({
dataSet: dataSet
});
}
render() {
return (
<div className="example">
<h2>HTML Table</h2>
<NewKSParser.HTMLTable dataSet={this.state.dataSet} id="myid" />
</div>
);
}
}
export default NewTable;
My child component should then pick up the dataSet
from props and display it:
export class HTMLTable extends Component {
constructor(props) {
super(props);
this.state = {
columns: [],
rows: []
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.dataSet !== this.props.dataSet) {
this.createTableData();
}
}
createTableData = () => {
const dataSet = { ...this.props.dataSet };
const rows = dataSet.dataSets ? dataSet.dataSets[0].rows : [];
const metaCols = dataSet.dataSets ? dataSet.dataSets[0].metadata.columns : [];
const columns = this.generateColumns(metaCols);
this.setState({
columns: columns,
rows: rows
});
}
generateColumns = (metaCols) => {
const columns = metaCols.map((col) => {
let obj = {};
obj.id = col.id;
obj.index = col.index;
obj.title = col.title;
return obj;
});
return columns;
};
render() {
const { rows, columns } = this.state;
const tableHeader = columns.map((column) => {
return <th key={column.id}>{column.title}</th>
});
const tableCells = rows.map((row, idx1) => {
return (<tr key={'row_' + idx1}>
{row.cells.map((cellContent, idx2) => <td key={`cell_${idx1}_${idx2}`}>{cellContent}</td>)}
</tr>);
});
return (
<table id={this.props.myid} key={this.props.myid}>
<thead>
<tr>
{tableHeader}
</tr>
</thead>
<tbody>
{tableCells}
</tbody>
</table>
);
}
}
As you can see, I have put that code in componentDidUpdate
because I was not getting it to work at all when putting it in the componentDidMount
method of the child.
It seems strange to me to put it in componentDidUpdate
, and I don't know if that is correct.
The second problem I am having is that it renders fine the first time I visit the page, but if I go to another page (within react-router) and then come back, the data in the table cells is gone. That could be either because I am implementing the life cycle wrong, or because there is something wrong with my keys... I don't know.
UPDATE:
This is my routing code:
class KnowledgeSets extends Component {
render() {
return (
<Router>
<main>
<TopMenu />
<Switch>
<Route exact path="/" render={()=><Home {...this.props} />} />
<Route path="/example" render={()=><Example {...this.props} />} />
<Route path="/htmltablecomponent" render={()=><Table {...this.props} />} />
<Route path="/complexexpandable" render={()=><ComplexExpandable {...this.props} />} />
<Route path="/newdataviewer" render={()=><NewDataViewer {...this.props} />} />
<Route path="/newhtmltablecomponent" render={()=><NewTable {...this.props} />} />
<Route path="/search" render={()=><Search {...this.props} />} />
</Switch>
</main>
</Router>
);
}
}
export default KnowledgeSets;
And if I change the child component to...
componentDidMount() {
console.log(this.props.dataSet);
this.createTableData();
}
the logged output is {}
. It never gets updated even though the sate of the parent has changed.