react.js change state of the topmost parent element from a deeply nested child

3.3k views Asked by At

I am using react-async along with node.js. React-async is used to use react.js in the asynchronous way. To make ajax calls, I am using super-agent. The PostList Component is the topmost parent component. Through its setTopmostParentState method, the component's state can be changed. I want to call this method from Comp4. The parameter to this method will come from an ajax call using the 'super-agent' node middleware.

How can I change the state from Comp4 ? If it was a parent component and then a child component issue, then the state-changing method could be easily passed as a prop to the child component. But a few nestings make it difficult to pass the parameter from the deeply nested child to the topmost parent.

Code Snippet:

    var Comp4 = React.createClass({

      clickHandler: function() {

        request.get('/api/prods_find/' + cat_id + '/' + subcat_id,

          function(resp) {

            var prods_menu = resp.body; 

* * * //CALL setTopmostParentState of the PostList component with the value prods_menu***

          });

      },
      render: function() {
        '<div onClick={this.clickHandler}>Target element</div>'

      }

    });

    var Comp3 = React.createClass({

      render: function() {

        < Comp4 > < /Comp4>

      }

    });

    var Comp2 = React.createClass({

      render: function() {

        < Comp3 > < /Comp3>

      }

    });

    var Comp1 = React.createClass({

      render: function() {

        < Comp2 > < /Comp2>

      }

    });

    var PostList = React.createClass({

      mixins: [ReactAsync.Mixin],

      getInitialStateAsync: function(cb) {

        request.get('http://localhost:8000/api/posts', function(response) {

          cb(null, {
            prods_menu: response.body
          });

        });

      },
      setTopmostParentState: function(prods_menu) {

        this.setState({

          prods_menu: prods_menu
        });
      },
      render: function() {

        var prods = this.state.prods_menu.prods;

        var menu = this.state.prods_menu.menu;

        return (

          < Comp1 > < /Comp1>

        );
      }

    });
2

There are 2 answers

0
Breno Ferreira On

In your specific case, I would suggest passing down a function to be called by your deepest child Component.

var Comp4 = React.createClass({

      clickHandler: function() {

        request.get('/api/prods_find/' + cat_id + '/' + subcat_id,

          function(resp) {

            var prods_menu = resp.body;
            this.props.updateParentState(prods_menu);

          });

      },
      render: function() {
        '<div onClick={this.clickHandler}>Target element</div>'

      }

    });

    var Comp3 = React.createClass({

      render: function() {

        < Comp4 {...this.props} > < /Comp4>

      }

    });

    var Comp2 = React.createClass({

      render: function() {

        < Comp3 {...this.props} > < /Comp3>

      }

    });

    var Comp1 = React.createClass({

      render: function() {

        < Comp2 {...this.props} > < /Comp2>

      }

    });

    var PostList = React.createClass({

      mixins: [ReactAsync.Mixin],

      getInitialStateAsync: function(cb) {

        request.get('http://localhost:8000/api/posts', function(response) {

          cb(null, {
            prods_menu: response.body
          });

        });

      },
      setTopmostParentState: function(prods_menu) {

        this.setState({

          prods_menu: prods_menu
        });
      },
      render: function() {

        var prods = this.state.prods_menu.prods;

        var menu = this.state.prods_menu.menu;

        return (

          < Comp1 updateParentState={this.setTopmostParentState} > < /Comp1>

        );
      }

    });

But as you can see, this is quite cumbersome. That's why Flux was created. Using Flux, you could Dispatch a message to a Store containing the new data, and the Store would notify your Component, re-rendering it.

You can learn more about Flux here https://facebook.github.io/flux/

0
SM79 On

Not saying it is perfect, but in situation like this I am using PubSub library for component communication. For example https://www.npmjs.com/package/pubsub-js.

In your topmost component you can do this:

var EVENT_NOTIFY_TOPMOST = "EVENT_NOTIFY_TOPMOST";
componentDidMount: function() {
    eventToUnsubscribe = PubSub.subscribe(EVENT_NOTIFY_TOPMOST, function( msg, data ){
        console.log( data ); //logs "world"
    });
}

componentDidUnmount: function() {
    //don't forget this
    PubSub.unsubscribe(eventToUnsubscribe )
}

And in your deepest child you can do this:

PubSub.publish(EVENT_NOTIFY_TOPMOST , "world");