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:
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
inMainContainer
and pass the selectcallback three levels down to
SelectableList
, handling all thepossible 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 resultgiven the same props and state, you can use
React.PureComponen
t for aperformance 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