Search code examples
javascriptreactjslifecycle

What is the correct pattern in ReactJS lifecycle (v. 16.4) to pass data


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.


Solution

  • React knows that the props of your child component have changed, and will re-render the component for you. You don't need to use any lifecycle methods or state in the child. Just regenerate rows and columns from the props inside the child's render method and use them right away.