ReactJS - Lifting state up vs keeping a local state

At my company we're migrating the front-end of a web application to ReactJS.

We are working with create-react-app (updated to v16), without Redux.

Now I'm stuck on a page which structure can be simplified by the following image:

Page structure

The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount() method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:

state.allData = {

left: {

data: [ ... ]

},

right: {

data: [ ... ],

pins: [ ... ]

}

}

LeftContainer receives as prop state.allData.left from MainContainer and passes props.left.data to SearchableList, once again as prop.

RightContainer receives as prop state.allData.right from MainContainer and passes props.right.data to SelectableList and props.right.pins to Map.

SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.

I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render() is called in both components.

It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.

As feasible alternative I thought of adding a _selected property to each item in state.right.data in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate() to avoid useless render() especially in LeftContainer.

Which is / could be the best solution to optimise this page from an architectural and performance point of view?

Below you have an extract of my components to help you understand the situation.

MainContainer.js

class MainContainer extends React.Component {

constructor(props) {

super(props);

this.state = {

allData: {}

};

}

componentDidMount() {

fetch( ... )

.then((res) => {

this.setState({

allData: res

});

});

}

render() {

return (

<div className="main-container">

<LeftContainer left={state.allData.left} />

<RightContainer right={state.allData.right} />

</div>

);

}

}

export default MainContainer;

RightContainer.js

class RightContainer extends React.Component {

constructor(props) {

super(props);

this.state = {

selectedItems: [ ... ]

};

}

onDataSelection(e) {

const itemId = e.target.id;

// ... handle itemId and selectedItems ...

}

render() {

return (

<div className="main-container">

<SelectableList

data={props.right.data}

onDataSelection={e => this.onDataSelection(e)}

selectedItems={this.state.selectedItems}

/>

<Map

pins={props.right.pins}

selectedItems={this.state.selectedItems}

/>

</div>

);

}

}

export default RightContainer;

Thanks in advance!

Answer

As React docs state

Often, several components need to reflect the same changing data. We

recommend lifting the shared state up to their closest common

ancestor.

There should be a single “source of truth” for any data that changes

in a React application. Usually, the state is first added to the

component that needs it for rendering. Then, if other components also

need it, you can lift it up to their closest common ancestor. Instead

of trying to sync the state between different components, you should

rely on the top-down data flow.

Lifting state involves writing more “boilerplate” code than two-way

binding approaches, but as a benefit, it takes less work to find and

isolate bugs. Since any state “lives” in some component and that

component alone can change it, the surface area for bugs is greatly

reduced. Additionally, you can implement any custom logic to reject or

transform user input.

So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems as a state in the RightContainer is completely justified and a good approach, since the parent doesn't need to know about and this data is being shared by the two child components of RightContainer and those two now have a single source of truth.

As per your question:

As feasible alternative I thought of adding a _selected property to

each item in state.right.data in MainContainer and pass the select

callback three levels down to SelectableList, handling all the

possible actions in MainContainer

I wouldn't agree that this is a better approach than the first one, since you MainContainer doesn't need to know the selectedItems or handler any of the updates. MainContainer isn't doing anything about those states and is just passing it down.

Consider to optimise on performance, you yourself talk about implementing a shouldComponentUpdate, but you can avoid that by creating your components by extending React.PureComponent which essentially implements the shouldComponentUpdate with a shallow comparison of state and props.

According to the docs:

If your React component’s render() function renders the same result

given the same props and state, you can use React.PureComponent for a

performance boost in some cases.

However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.

For example consider the following case

const App = () => {

<Router>

<Route path="/" component={Home}/>

<Route path="/mypage" component={MyComp}/>

</Router>

}

Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render prop. However it would easily be done by connecting both of these components to Redux state using a connect function like

const mapStateToProps = (state) => {

return {

data: state.data

}

}

export connect(mapStateToProps)(Home);

and similarly for MyComp. Also its easy to configure actions for updating relevant informations

Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well

以上是 ReactJS - Lifting state up vs keeping a local state 的全部内容, 来源链接: utcz.com/a/23571.html

回到顶部