Why use getDerivedStateFromProps when you have componentDidUpdate? - javascript

I'm confused about the new lifecycle of react 16, getDerivedStateFromProps use case. Take below code for example, getDerivedStateFromProps is not needed at all since I can achieve what I want with componentDidUpdate.
export class ComponentName extends Component {
//what is this for?
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.filtered !== prevState.filtered && nextProps.filtered === 'updated') {
return {
updated: true //set state updated to true, can't do anything more?
};
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if(prevProps.filtered !== this.state.filtered && this.state.filtered === 'updated'){
console.log('do something like fetch api call, redirect, etc..')
}
}
render() {
return (
<div></div>
);
}
}

From this article:
As componentWillReceiveProps gets removed, we need some means of updating the state based on props change — the community decided to introduce a new — static — method to handle this.
What’s a static method? A static method is a method / function that exists on the class not its instance. The easiest difference to think about is that static method does not have access to this and has the keyword static in front of it.
Ok, but if the function has no access to this how are we to call this.setState? The answer is — we don’t. Instead the function should return the updated state data, or null if no update is needed
The returned value behaves similarly to current setState value — you only need to return the part of state that changes, all other values will be preserved.
You still need to declare the initial state of the component (either in constructor or as a class field).
getDerivedStateFromProps is called both on initial mounting and on re-rendering of the component, so you can use it instead of creating state based on props in constructor.
If you declare both getDerivedStateFromProps and componentWillReceiveProps only getDerivedStateFromProps will be called, and you will see a warning in the console.
Usually, you would use a callback to make sure some code is called when the state was actually updated — in this case, please use componentDidUpdate instead.

With componentDidUpdate you can execute callbacks and other code that depends on the state being updated.
getDerivedStateFromProps is a static function and so has no access to the this keyword. Also you wouldn't have any callbacks placed here as this is not an instance based lifecycle method. Additionally triggering state changes from here could cause loops(e.g. with redux calls).
They both serve different fundamental purposes. If it helps getDerivedStateFromProps is replacing componentWillReceiveProps.

getDerivedStateFromProps basically can save you one render cycle. Let's say due to some props change, you have to update some state and the UI responds with the new state. Without getDerivedStateFromProps, you have to wait until componentDidUpdate is invoked to make the prop comparison and call setState to update the UI. After that, componentDidUpdate is invoked again and you have to pay attention to avoiding endless rendering. With getDerivedStateFromProps, the UI update just happens earlier.

Related

Why does componentWillRecieveProps() is called before the page is re-rendered?

I'm trying to re-render some values in my react-native app.
I have used componentWillRecieveProps() for that.
But, componentWillRecieveProps() is executed before re-render,setting wrong values in state.
componentWillReceiveProps(nextProps) {
const {
navigation
} = nextProps;
const flatListData = navigation.getParam("flatListData", "NO-DATA")
console.log(flatListData)
this.setState({
dataSource: flatListData
})
}
componentWillRecieveProps will always executes before re-render, it's default behavior.
You probably need componentDidUpdate which will execute after re-render.
Note: componentWillRecieveProps is replaced by getDerivedStateFromProps
Reference to this information.
The component method componentWillReceiveProps() is deprecated in later react versions.
You probably need componentDidUpdate.
Another problem I can see in the code is setting of state from props this is a common anti pattern and should be avoided. You can read more about better alternatives here
It's not clear why you're requesting navigation.getParam in componentWillReceiveProps. They're two very different things. If this component is a react navigation route, then parameters will be set. If this component is a child of another component then componentWillReceiveProps will be called with updated parameters when the parent component changes parameters.

re-render component without setting state react js

I am deleting a record from db, for this I am calling an API. When I received an
API response of a successful deletion, I need to re-render all the component again like reload does. I tried it with this.forceUpdate and shouldComponentAgain but no luck.
I also tried with componentDidUpdate, it works but it is calling API infinite times. Below is my code how I used componentDidUpdate:
componentDidUpdate(){
let newThis = this;
getAccounts().then(function(response){
if(response.status===200){
newThis.setState({
Accounts:response.data
})
}
});
}
Please tell me the way to re-render like reload do, but without re-loading the whole page.
When using componentDidUpdate, you should always have a conditional setState which denotes that you need to perform something because the current state or current props is not equal to previous state or props.
componentDidUpdate always gets called whenever your component has updated. In your case what is happening is that you are calling setState without any condition which updates your component, and setState is called again causing an infinite loop in updating the component.
You should have something like this check here:
componentDidUpdate(prevProps, prevState){
let newThis = this;
if(newThis.props.{some-variable} !== prevProps.{some-variable}) {
getAccounts().then(function(response){
if(response.status===200){
newThis.setState({
Accounts:response.data
})
}
});
}
}
Adding conditional setState is very important here else you will end up in an infinite loop.
As per the official docs as well:
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition or you’ll cause an infinite
loop. It would also cause an extra re-rendering which, while not
visible to the user, can affect the component performance. If you’re
trying to “mirror” some state to a prop coming from above, consider
using the prop directly instead.
Hope it helps.
If you want to render the component again, then change the props from the parent. If props change then child component automatically going to render. And by this features, you can also render the selective component.

componentWillReceiveProps vs getDerivedStateFromProps

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.

Which React lifecycle method is a good replacement for componentWillRecieveProps?

I have heard of getDerivedStateFromProps but it does not work how I want it to.
I have this code:
class App extends Component {
static getDerivedStateFromProps(props, state) {
console.log("Get derived state from props called");
return null;
}
render() {
return (
<button onClick={() => this.setState({})}>Change State</button>
);
}
}
Clicking the button calls getDerivedStateFromProps. This is not what I need. I want a function that is called only when new props are received. When internal state changes, this function should not be called.
I intend to use this in a modal form scenario. When the component receives an object as a prop, the component should translate this object into a form, then place that form into the state. If I use getDerivedStateFromProps, since it is called in every this.setState(), it would not reflect the changes in the form since whenever the user types, a this.setState() is fired, and instead of the changes being set into the state, the initial object is.
Which React lifecycle method should I use?
There are a couple of ways to approach this problem depending on different use cases.
If new props are added, those new props can be checked in the Child component and user defined functions can be called in case the new object exists.
getDerivedStateFromProps(props, state, prevProps) is a static method and this does not exist in the same, which means that you'll have to return a state based on various conditions. That is, let it work on default on most cases. On cases where you want to alter its functionality, place checks and return the modified state.
Hope it answers :)

How to properly handle subscriptions depending on property

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.

Categories