Reactjs state changes do not propagate to dynamically-created children components

1.2k views Asked by At

I'm learning react and while working on a bigger project I created this mockup to show the issue I'm having.

The parent component maintains a value in state which it passes to children via props. I want this value to propagate to children and update there when it is changed in the parent state. This works in the first version of this code:

import React from "react"
import Child from './Child'

export default class Parent extends React.Component {
    
    constructor() {
        super();
        this.state = {
            single_val: false,
        }
    }

    render() {

        return(
            <div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
                <p>Parent val: {this.state.single_val.toString()}</p>
                <Child parent_val={this.state.single_val}/>
                <Child parent_val={this.state.single_val}/>
                <Child parent_val={this.state.single_val}/>
                <div className="switch" 
                    style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
                    onClick={(e)=>{this.setState({single_val: true})}}
                >
                </div>
            </div>
        )
    }
}

However, in the final version of the project, I need to create the children dynamically. I do it like this:

import React from "react"
import Child from './Child'

export default class Parent extends React.Component {
    
    constructor() {
        super();
        this.state = {
            single_val: false,
            children_divs: [],
        }
        this.setUp = this.setUp.bind(this);
    }

    componentDidMount() {
        this.setUp();
    }

    setUp() {

        var baseArray = [...Array(3)];
        var children = baseArray.map((elem)=>{
            return (<Child parent_val={this.state.single_val} />)
        });

        this.setState({children_divs: children});
    }

    render() {

        return(
            <div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
                <p>Parent val: {this.state.single_val.toString()}</p>
                
                {this.state.children_divs}

                <div className="switch" 
                    style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
                    onClick={(e)=>{this.setState({single_val: true})}}
                >
                </div>
            </div>
        )
    }
}

...and the value no longer propagates to children when I press the button and change the parent's state: results screenshots.

How to keep the dynamic creation of child divs and still have the parent value propagate? I sense the issue might me because the value and children divs array are both maintained in the parent state but I'm not sure how to fix it. Hours of searching and looking at examples suggest I should recreate children divs from scratch - run the setUp again - but it seems like an overkill for one state value that I thought should propagate anyway.

Child component code for reference:

import React from "react"

export default function Child(props) {
    return (
        <div className="Child">
            <p>Child val: {props.parent_val.toString()}</p>
        </div>
    )
}

P.S. I even experimented with adding componentDidUpdate() to children to try and receive props again, but it never triggered.

1

There are 1 answers

4
Sagar More On BEST ANSWER

Ok, so the problem here is your children_divs are created once and value of single_val is added/sent to them at that time (when you have created them in setUp function. Solution is simple, have your children created in render function, as render is called each time your state changes. This also removes your children_divs from state as its only used to render and serve no other purpose.

import React from "react"
import Child from './Child'

export default class Parent extends React.Component {
    
    constructor() {
        super();
        this.state = {
            single_val: false,
        }
    }

    render() {
        var baseArray = [...Array(3)];
        var children_divs= baseArray.map((elem)=>{
            return (<Child parent_val={this.state.single_val} />)
        });

        return(
            <div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
                <p>Parent val: {this.state.single_val.toString()}</p>
                
                {children_divs}

                <div className="switch" 
                    style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
                    onClick={(e)=>{this.setState({single_val: true})}}
                >
                </div>
            </div>
        )
    }
}