简体   繁体   中英

Updating child props from parent state in ReactJS

I'm trying to understand how I can structure a ReactJS app with different "pages" or "views".

I have the following component as my base app and I'm using a currentState property in the React state to switch between which Components are active in the view.

class App extends React.Component {

    constructor(props) {
        super(props);
        this.state = {currentState: 'Loading', recipes: []};
        this.appStates = {
            'Loading': <Loading/>,
            'Home': <RecipeList recipes={this.state.recipes}/>
        }
    }

    dataLoaded(data) {
        this.setState({
            recipes: data,
            currentState: 'Home'
        });
    }

    componentDidMount() {

        // AJAX Code that retrieves recipes from server and then calls dataLoaded
    }

    render() {
        return this.appStates[this.state.currentState];
    }
}

Which does the job, but the component never receives the updated recipes array when the dataLoaded callback is fired.

How can I cause the to update its props based on the updated state in the App?

Or am I approaching this whole thing the wrong way?

I think that your aproach isn't really react-like, and you have at least a couple of concepts that can be improved.

First of all, I would definitely use react-router to achieve any complex navigation between pages/Components in React.js. Implementing it yourself is more complicated and error-prone. react-router will allow you to assign Components to different routes easily.

Second, I think that you should almost never store things in the context this directly. Basically because it leads to errors like yours here: not realizing that appStates isn't changing at all. React's state is a great tool (which must sometimes be replaced/complemented with others like Redux) to store your application's state.

In the case of storing in the state what should be rendered in the Component, you should probably complement the react-router functionality with simple flags in the state initializated in the constructor that allow you to know what should you return in the render function.

Here is an example that shows how can you tell a component to change its view dynamically between loading and loaded by using just React's state. Of course, you could recreate a very similar behaviour making an AJAX call in componentDidMount and changing the state to stop loading when it's done instead of using a button.

 class App extends React.Component { constructor(props) { super(props); this.state = {loading: true}; this.stopLoading = this.stopLoading.bind(this); } stopLoading() { this.setState({ loading: false, }); } render() { let view=<div><h1>loading</h1><button onClick={this.stopLoading}>FINISH</button></div>; if(!this.state.loading){ view=<h1>loaded</h1>; } return <div>{view}</div>; } } ReactDOM.render( <App />, document.getElementById('container') ); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="container"></div> 

Your constructor method is only executed when the component mounts, at which point recipes is empty, and passes that empty array to appStates. Essentially, appStates never changes.

What I would recommend doing is pushing all the component code in the constructor to inside the render function. The beauty of react is that you can completely re-render everything at very little cost. React will do the hard work of comparing the difference in DOMs and only re-render the minimal differences.

I agree with @Ezra Chang. I think the code could be adjusted making use of just the state and the javascript spread function to pass the App props to the child RecipeList:

class App extends React.Component {

   constructor(props) {
      super(props);
      this.state = {currentState: 'Loading', recipes: [],'Loading': <Loading/>,
        'Home': <RecipeList recipes={this.state.recipes} {...this.props}/>};
   }

   //I assume you want to pass the props of this App component and the data from the back-end as props to RecipeList. 
   dataLoaded = (data) => {
       this.setState({
          recipes: data,
          currentState: 'Home',
          'Loading': <Loading/>,
          'Home': <RecipeList recipes={data} {...this.props}/>
       });
    }

    componentDidMount() {
       // AJAX Code that retrieves recipes from server and then calls dataLoaded
    }

    render() {
       return this.state[this.state.currentState];
    }
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM