Search code examples
javascriptreactjsinheritancemultiple-inheritance

Dealing with Nested React Components' State Changes


So, I have multiple ReactComponent. Initially, I was thinking that there will be sort of a parent component (let's call this GrandPa) with its own state, and it will pass down some info about its state to another component (call it Parent). Similarly, Parent passes some of his to Child and child to GrandChild. So we have the hierarchy:

GrandPa -> Parent -> Child -> GrandChild

However, I quickly realized that when I change the state of GrandPa with method this.setState(prevState => (<new state based on prevState>)), the changes do not cascade down the channel. Another problem I realized is passing changes up the channel as well. I have not found a nice way to do this. (Before, I did something super sketchy by binding some events to a component in the DOM and having a public variable that is accessible from all of the ReactComponent. Then when anyone triggers a specific event, I would try to trigger events that would update the respective components... Long story short, this created a lot of problems and was ugly.)

So, I have the example code below where I tried to do this with just a parent and child. It's a dummy example, but it's just to learn how to do this. The parent, called ReactRandom has a state {name: <name>} and has a method that changes that name to a random string. When ReactRandom renders, it renders that name from another component ReactRandomNested which basically prints out the name passed via its own props. Here, I created a method inside of ReactRandomNested which explicitly renders the changes (which I DO NOT WANT BTW, I am thinking there is a way to cascade the updates down the channel without having to explicitly do that because it'd get nasty quicky as we have >2 nested levels). However, even this does not work. It has a 1 step delay and renders the previous update at the current timestep.

Anyway, here's the code I currently have:

class RandomReactNested extends React.Component {
    constructor(props) {
        super(props);
        this.state = {name: props.name};
    }

    handleChange() {
        let self = this;
        self.setState({name: self.props.name});
    }

    render() {
        let self = this;
        return React.createElement("span", {
            onClick: () => self.handleChange.call(this)
        }, self.state.name);
    }
}

class RandomReact extends React.Component {
    constructor (props){
        super(props);
        this.state = {name: props.name};
        this.changeName.bind(this);
    }

    changeName () {
        let random_word_length = Math.round(Math.random()*20),
            index_count = 0,
            new_name = "",
            alphabet = "abcdefghijklmnopqrstuvwxyz";
        for (index_count; index_count <= random_word_length; index_count += 1) {
            new_name += `${alphabet[Math.round(Math.random()*random_word_length)]}`;
        }
        console.log(new_name);

        this.setState(prevState => ({
            name: new_name
        }));
    }

    render() {
        let self = this;
        return React.createElement("div", {
            key: 2,
            onClick: () => (this.changeName.call(self))
        },
            React.createElement(RandomReactNested, {
                name: self.state.name
            }));
    }
}

By the way, I am quite new to React and am trying to learn how to use this effectively. I also saw this from the ReactJS website toward the end of the page (https://reactjs.org/docs/composition-vs-inheritance.html), which seems to advice against this (but I am not entirely sure?):

"So What About Inheritance? At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies. Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions. If you want to reuse non-UI functionality between components, we suggest extracting it into a separate JavaScript module. The components may import it and use that function, object, or a class, without extending it."


Solution

  • cough, redux, cough

    Anyway, passing props is a one way street! You can pass things down, and use them, but you can't pass them up. The only way to manipulate a parent component is through passing an entire function as props.

    updateGrandpaState(newData){
    this.setState({ originalData: newData})
    }
    

    From your grandpa component you you would pass it down like this...

    <ParentComponent updateGrandpaState={this.updateGrandpaState} />
    

    And inside your parent component you can call

    this.props.updateGrandpaState(parentComponentData)
    

    Which will then fire the original function that lives in grandpaComponent, thus updating grandpaComponent State.

    And yes, you can go further, forewarning, the deeper you go the uglier it gets...

    Inside Grandpa

    < ParentComponent updateGrandpaState={this.updateGrandpaState} /> 
    

    Inside Parent

    < ChildComponent updateGrandpaState={this.props.updateGrandpaState} />
    

    Inside Child

    this.props.updateGrandpaState(childComponentData)
    

    When you go two levels deep I also console.log(this.props) on componentDidMount while doing dev work.

    To make things even cooler, you could even pass grandpaState down into ParentComponent to use, which you could hook up to componentWillRecieveProps.....

    <ParentComponent grandpaState={this.state} updateGrandpaState={this.updateGrandpaState} />
    

    But honestly, once you learn redux, set it up a few times, it's really awesome and I would never not use it!