What is the best place to store the result of an expensive calculation from the React props which I use in render() but do not want to execute at each render() ?
constructor(props) {
super(props)
const result = this.doExpensiveCalculation(props)
}
componentWillReceiveProps(nextProps) {
// if nextProps differ from props
const result = this.doExpensiveCalculation(nextProps)
}
doExpensiveCalculation(props) {
// Some expensive stuff
}
render(){
// Use doExpensiveCalculation(this.props) here
}
The options are this and state but both I see rather unsatisfying. Is there a ready solution which uses memoisation?
On the other hand, should I worry about optimizing this ? I read that React can rerender component even if the props have not changed but does this happen often ?
You can handle the re-rendering in the lifecycle method of shouldComponentUpdate. Default value is always return true. By returning false there React will not re-render the component.
See the docs for more. Besides that, React only updates if a state change occurs since props are read-only.
Your options are to store it as you suggested or have a class with a static field to keep it there.
If all you want to do is perform the expensive calculation whenever you get new props, instead of on every render, you probably want componentWillReceiveProps:
componentWillReceiveProps() is invoked before a mounted component receives new props.
As far as where to store them, you can either store them in state, or as a property directly on the component instance. Either will work just as well.
You want to make sure compare values though, to avoid unnecessarily recomputing.
For example:
componentWillReceiveProps(nextProps) {
if (nextProps.someValue !== this.props.someValue) {
this.someResult = this.performExpensiveCalculation(nextProps.someValue);
}
}
Related
I know we can easily send the content of mapStateToProps in the component's state by doing so :
constructor(props){
super(props);
this.state = {
filteredApps: this.props.apps
}
}
In this usecase, this.state.filteredApps gets filled with what was mapped to props from Redux.
But what if this.props.apps is only filled properly after an async call? In an async context, this.props.apps will probably be an empty array for when it is initialized until the real data is fetched. Take this as an example :
class AppFilterer extends React.Component {
constructor(props) {
super(props);
this.state = {
filteredApps : this.props.apps
}
}
componentWillMount() {
this.props.getApps();
}
render(){ return <div> </div> }
}
const mapStateToProps = state => {
let { apps } = state.Admin;
return { apps };
};
export default connect(mapStateToProps, { getApps })(AppFilterer);
In this case, my Redux action (which is caught by an Saga) this.props.getApps(); is the call that fills my props full of apps and is called from the componentWillMount function. It is initialized as an empty array and then gets filled with apps once the call is complete.
I wish to filter these apps once they are fetched from the API so want to put them inside my component's state so that I don't mess with the Redux state. What is the best practice for updating the component's state in this case? In other words, is there any way to take the result of a saga that has been mapped to props and set it into the component's state or am I looking for a weird pattern and should filter it some other way?
First of all API calls go in componentDidMount not in componentWillMount which is also now deprecated. Please refer this guide:
https://reactjs.org/docs/react-component.html
Secondly, when you are using redux state and mapping it to props, you should not set that in your component local state, that’s not a good practice. You’ll receive updated props when your promise will return and you can always rely on props in that scenario.
But if you still want to do that you can override componentDidUpdate(prevProps) which will be called when your props or state is updated. Here is where you can set your state if you still want to do that.
Note for your filter thing
You can do filtering in componentDidUpdate method like:
this.setState({filteredApps. this.props.apps.filter(<your filter logic>)})
What exactly componentWillReceiveProps and getDerivedStateFromProps are subtle question for me. Because, I just came across to an issue while using getDerivedStateFromProps:
// Component
state = {
myState: []
}
// Using this method works fine:
componentWillReceiveProps(nextProps) {
this.setState({
myState: nextProps.myPropsState
})
}
// But using this method will cause the checkboxes to be readonly:
static getDerivedStateFromProps(nextProps,prevProps) {
const { myPropsState: myState } = nextProps
return {
myState
}
}
// And here's checkbox
<input type="checkbox" id={`someid`}
onChange={(e) => this.handleMethod(e, comp.myState)}
checked={myState.indexOf(comp.myState) > -1} />
React version: 16.4.1
getDerivedStateFromProps is not a direct alternative to componentWillReceiveProps, purely because of the fact that its called after every update, whether its the change in state or change in props or re-render of parent.
However whatever is the case, simply returning the state from getDerivedStateFromProps is not the right way, you need to compare the state and props before returning the value. Else with every update the state is getting reset to props and the cycle continues
As per the docs
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
This method exists for rare use cases where the state depends on
changes in props over time. For example, it might be handy for
implementing a <Transition> component that compares its previous and
next children to decide which of them to animate in and out.
Deriving state leads to verbose code and makes your components
difficult to think about. Make sure you’re familiar with simpler
alternatives:
If you need to perform a side effect (for example, data fetching
or an animation) in response to a change in props, use
componentDidUpdate lifecycle instead.
If you want to re-compute some data only when a prop changes, use
a memoization helper instead.
If you want to “reset” some state when a prop changes, consider
either making a component fully controlled or fully uncontrolled
with a key instead.
P.S. Note that the arguments to getDerivedStateFromProps are props and state and not nextProps and prevProps
To get into more details,
In order to make changes based on props change, we need to store prevPropsState in state, in order to detect changes. A typical implementation would look like
static getDerivedStateFromProps(props, state) {
// Note we need to store prevPropsState to detect changes.
if (
props.myPropsState !== state.prevPropsState
) {
return {
prevPropsState: state.myState,
myState: props.myPropsState
};
}
return null;
}
Finally, I resolved my issue. It was a painful debugging:
// Child Component
// instead of this
// this.props.onMyDisptach([...myPropsState])
// dispatching true value since myPropsState contains only numbers
this.props.onMyDispatch([...myPropsState, true])
This is because, I have two conditions: 1) on checkbox change (component) 2) on reset button pressed (child component)
I was needing to reset the states when reset button is pressed. So, while dispatching state to the props for reset button, I used a boolean value to know it's a change from the reset. You may use anything you like but need to track that.
Now, here in the component, I found some hints to the differences between componentWillReceiveProps and getDerivedStateFromProps after debugging the console output.
// Component
static getDerivedStateFromProps(props, state) {
const { myPropsState: myState } = props
// if reset button is pressed
const true_myState = myState.some(id=>id===true)
// need to remove true value in the store
const filtered_myState = myState.filter(id=>id!==true)
if(true_myState) {
// we need to dispatch the changes to apply on its child component
// before we return the correct state
props.onMyDispatch([...filtered_myState])
return {
myState: filtered_myState
}
}
// obviously, we need to return null if no condition matches
return null
}
Here's what I found the results of the console output:
getDerivedStateFromProps logs immediately whenever props changes
componentWillReceiveProps logs only after child propagates props changes
getDerivedStateFromProps doesn't respond to the props changes ( I meant for the dispatch changes as in the example code)
componentWillReceiveProps responds to the props changes
Thus, we needed to supply the changes to child component while using getDerivedStateFromProps.
The process of pasting true value in the state I require because getDerivedStateFromProps handle all the changes unlike componentWillReceiveProps handles only the child component dispatches the changes to the props.
By the way, you may use custom property to check if it is changed and update the value if getDerivedStateFromProps but for some reason I have to tweak this technique.
There might be some confusion on my wording but I hope you'll get it.
From the react docs:
Note that this method is fired on every render, regardless of the cause. This is in contrast to UNSAFE_componentWillReceiveProps, which only fires when the parent causes a re-render and not as a result of a local setState.
You are effectively overriding your state with the current props every time after calling setState(). So when you check a box (e) => this.handleMethod(e, comp.myState) is called which is assume calls setState() to update the checked state of the checkbox. But after that getDerivedStateFromProps() will be called (before render) that reverts that change. This is why unconditionally updating state from props is considered an anti-pattern.
I have a global service widgetService which holds data for a number of widgets, each identified by a widgetID. Each widget's data can change at any time. I want to display a widget with a React component, say WidgetReactComponent.
The react component shall take a widget ID as property and get the information to display from the widget service. A widget's data can be queried from the widget service with the method getWidgetData(widgetID). And in order to be able to publish data changes, it also offers two methods: addListenerForWidget(widgetID, listener) and removeListenerForWidget(widgetID, listener).
When assuming that the property is set once and never changed, this can be achieved like this, following React's recommendations:
class WidgetReactComponent extends Component {
constructor() {
super();
this.state = {
data: widgetService.getWidgetData(this.props.widgetID)
};
this._onDataChange = this._onDataChange.bind(this);
}
_onDataChange(newData) {
this.setState({data: newData});
}
componentDidMount() {
// React documentation: "This method is a good place to set up any subscriptions."
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
componentWillUnmount() {
// React documentation: "Perform any necessary cleanup in this method, such as [...] cleaning up any subscriptions that were created in componentDidMount()."
widgetService.removeListenerForWidget(this.props.widgetID, this._onDataChange);
}
render() {
return <div className="Widget">{this.state.data.stuff}</div>;
}
}
The component may then be used like this:
<ReactWidgetComponent widgetID={17} />
However, the widgetID property may change at any time, and the component has to handle this in order to function properly under all circumstances. By react's recommendation, this should be handled by setting the state based on properties using the static getDerivedStateFromProps function. But since it is static, I do not have access to the component and cannot change the listeners accordingly.
One way to work around this would be to store the widgetID in the state, and then use the lifecycle method componentDidUpdate to detect the change, like this:
constructor() {
super();
this._onDataChange = this._onDataChange.bind(this);
}
static getDerivedStateFromProps(nextProps) {
return {
widgetID: nextProps.widgetID,
data: widgetService.getWidgetData(nextProps.widgetID)
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.widgetID !== this.state.widgetID) {
widgetService.removeListenerForWidget(prevState.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.state.widgetID, this._onDataChange);
}
}
However, componentDidUpdate won't be called when shouldComponentUpdate returns false. This doesn't feel like a safe way of doing this. Also I believe the listeners will be wrong for the entire timespan between the property change and the update's completion. How could I safely implement this?
You don't need to store widgetID in state, you can compare prevProps with this.props:
componentDidUpdate(prevProps, prevState) {
if (prevProps.widgetID !== this.props.widgetID) {
widgetService.removeListenerForWidget(prevProps.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
}
You will also need to add the listener in componentDidMount as componentDidUpdate is not called on first rendering:
componentDidMount() {
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
Regarding your concerns:
componentDidUpdate won't be called when shouldComponentUpdate returns false
From the docs:
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props.
So if you decided to not update the component when this.props.widgetID changes, then you are violating the assumption/purpose of shouldComponentUpdate and should not expect your widget listener to be updated.
A lot of things will not work as intended if you misuse shouldComponentUpdate anyway (eg. component not updated to reflect new data), so relying on an API being used correctly as per official docs is a necessity to achieve simplicity, rather than something to be avoided.
the listeners will be wrong for the entire timespan between the property change and the update's completion
By this logic, when you update some displayed data in an event handler, you can also claim that the data displayed is wrong for the entire timespan between the event and the re-rendering. You can even claim that your text editor is displaying the wrong data between the time you press a keyboard key and rendering of the key on the screen.
I want to create an app with react and redux. My component subscribed to several states from the redux store, some of the state-data need to be prepared before the rendering can take place. Do I need to put the prepareData function into componentWillReceiveProps and write it to the state afterwards? It seems to create a lot of queries in the componentWillReceiveProps. Is there a best practice?
componentWillReceiveProps(nextProps) {
if (this.props.dataUser !== nextProps.dataUser) {
this.prepareData(nextProps.dataUser);
}
if (this.props.dataProject !== nextProps.dataProject) {
.....
}
if (this.props.dataTasks !== nextProps.dataTasks) {
.....
}
}
As Axnyff suggests, you can do your data preparation in mapStateToProps, this will trigger a render each time your redux state updates (your component can be stateless this way) :
mapStateToProps = (state) => {
const dataUserPrepared = prepareData(state.dataUser);
return { dataUser: dataUserPrepared };
}
If you have a lot of different data to prepare, which updates individually, that can be a loss in performance.
In this case you can use componentWillReceiveProps like in your question, this is fine because the setState in your prepareData() function will be batched with the received props to trigger only one render per prop update.
If you were using an app without redux then the solution would be to prepare your data before you call this.setState().
I believe the same solution applies to when using redux, your can prepare your data inside your action because you return the action object having a type and payload.
You can also prepare your data inside your reducer before returning the state object.
You could even prepare your data inside mapStateToProps of your component.
But in case you want to specific conditions under which component should re-render when state changes, then you do that in shouldComponentUpdate()
I am unclear about the use of this.state in React components. While I can create this.state.myvar, why should not I just create this.myvar?
class MyComponent extends Component {
state = {myvar: 123};
render() {
return <span>{this.state.myvar}</span>;
}
}
or
class MyComponent extends Component {
myvar = 123;
render() {
return <span>{this.myvar}</span>;
}
}
I realize that there are helpers like this.setState, but at the end this.state is just a convenience, right? Or does it play a bigger role in React? Should I avoid setting properties directly on this to store my state? If so, why?
No, in fact; rather wrong.
this.state in a react component is a special React-backed container that is only acknowledged as having been updated when you use setState, which triggers a re-render, which might cause DOM updates (or not, depending on what React's diff algorithm sees happening in the JS virtual dom).
You can, of course, also use object properties bound to this, but changing them does absolutely nothing for the component itself. React doesn't look at your full component instance for changes, it only looks (internally) at the state, and (when changed by parents) at the props.
As such, you can't "create" things like this.state.myvar and then expect them to actually exist from lifecycle function to lifecycle function: as a special management construct, any values you tack onto state outside of proper this.setState(...) calls have undefined behaviour. They might exist, or they might not. If you really are working with the internal state, then you need to signal changes via this.setState({ myvar: value }).
Of course, that doesn't mean you can't use this.myvar, that'll work fine, but changing it will not "do" anything other than literally just that.
When would you use this.val instead of state? When your component has to perform operations that lead to an "intermediate" state, neither being one renderable state, nor the next. For instance, when code can update a state value multiple times between renders, during those changes your component is in an intermediate state, and so you don't want it to re-render. In fact, expecting it to can lead to huge bugs:
...
doTest() {
this.setState({ val: this.state.val+1 });
this.setState({ val: this.state.val+1 });
this.setState({ val: this.state.val+1 });
},
...
This code will not yield a this.state.val that's 3 higher than before, because state updates are queued, and overwrite each other. Only the last instruction before a re-render "wins". So in that case you'd need something like:
...
doTest() {
var localval = this.state.val;
localval++;
localval++;
localval++;
this.setState({ val: localval });
},
...
And then if we also need that value accessible outside of this function, then we finally have a legitimate use for a this property:
...
doTest() {
this.accessibleval = this.state.val;
this.updateValueAFewTimes();
this.setState({ val: this.accessibleval });
},
...
this.state plays a larger role than you realize.
Setting state via this.setState triggers the component to re-render (among other things). Otherwise, React would have no way of knowing when something it depends on changed.