Where to move setState() callback from deprecated componentWillReceiveProps()? - reactjs

React 16 deprecates componentWillReceiveProps() lifecycle method. The preferred replacement is new getDerivedStateFromProps() or componentDidUpdate(). Now let's suppose I have some code like that:
componentWillReceiveProps(newProps) {
if (this.props.foo !== newProps.foo) {
this.setState({foo: newProps.foo}, () => this.handleNewFoo()});
}
}
I could try moving the code to getDerivedStateFromProps() like that:
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return {foo: newProps.foo};
} else {
return null;
}
}
but what do I do with the callback from setState()? Is moving it to componentDidUpdate the only option? I'd rather have this.handleNewFoo() invoked before render() to avoid visual and performance cost of two renders.

If you haven't read this blog post you should
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
If you are simply assigning props.foo to state.foo without any calculation / processing then you need to ask yourself should you really need that field in state. Because you can always use this.props.foo instead of this.state.foo.
what is this.handleNewFoo method does?
are you doing another setState inside handleNewFoo? if you are then you are triggering multiple render calls.
eg.
this.setState({foo: this.props.foo} // one render call, () => this.handleNewFoo())
const handleNewFoo = () => {
this.setState({ }) // another render call
}
If you are not doing any setState inside handleNewFoo then ask yourself does handleNewFoo has to be called on prop change?
If you are using setState inside the handleFoo i would suggest something like this
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return this.handleNewFoo(this.props.foo, prevState);
} else {
return prevState;
}
}
handleNewFoo(foo, state) {
// do some calculation using foo
const derivedFoo = state.foo + foo;
// if your are using immutability_helper library.
const derivedState = update(state, { foo: {$set: derivedFoo}});
return derivedState
}
It all depends on what you are doing inside this.handleNewFoo
EDIT:
Above code won't work as #Darko mentioned getDerivedStateFromProps is static method and you cannot access this inside it. And you cannot get the previous props inside getDerivedStateFromPropslink. So this answer is completely wrong.
one solution is to use useEffect
useEffect(() => {
setState(...)
}, [props.foo])
this useEffect will call whenever the props.foo is changed. And also the component will be rendered twice one for prop change and another for setState inside useEffect. Just be careful with setState inside useEffect as it can cause infinite loop.

Related

How to fire an action event in react 16.8?

I just came in react after an year and before that I used to use componentWillRecieveProps() to call api(fire an action) just after getting updated props.
componentWillRecieveProps(nextProps) {
if (nextProps.user !== this.props.user) {
//Some api call using user prop
}
}
But now componentWillRecieveProps is replaced by getDerivedStateFromProps and there I cannot use this inside it. So how can I dispatch an event in newer react version or what is the best way?
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.user !== prevState.user) {
this.fetchSetting();
return { user: nextProps.user };
}
return null;
}
fetchSetting = async () => {
const { fetchSetting } = this.props;
const data = await fetchSetting();
};
You can use the method componentDidUpdate(prevProps, prevState) which is a good replacement for componentWillReceiveProps().
Check the documentation here : componentDidUpdate (React.Component)
In you exemple you can do this :
static componentDidUpdate(prevProps, prevState) {
if (this.props.user !== prevProps.user) {
this.fetchSetting();
return { user: this.props.user };
}
return null;
}
But actually, if you really want to match the latest recommendations of React you shouldn't use classes but hooks which is aimed to be the new replacement. You can find more information about hooks on the page : Introducing Hooks
There are two ways you can achieve this: you can use the new useEffect hook or you can use componentDidUpdate to compare the props manually and call a method that updates the state based on that.
The preferred way by the React team is the functional way (yea I know it's weird) using this:
https://reactjs.org/docs/hooks-effect.html
If you want to keep using the class-based way then you can use componentDidUpdate:
https://reactjs.org/docs/react-component.html#componentdidupdate

Method called after latest render

In my reactjs application, in one component, render function is called many times. I have some data available only at the last time the render is called. I need to do some treatments on this data. Where can I do it?
componentWillMount() {
get(this.props, 'myarraydata', []).forEach(element => {
//here i will do some treatments for each elemment
});
}
render() {
console.log(get(this.props, 'myarraydata', []));
return (
<div>
<pre>{JSON.stringify(get(this.props, 'myarraydata', []), null, 2) }</pre>
</div>
);
}
As you can see, in my application, this render method is called many times, and it uses myarraydata passed from another component and myarraydata is available only when render is called for the last time (means when render is called for the first, second, .. times, my arraydata is empty). but the problem is that componentwillmount method is called only one time, and at that time, myarraydata still empty and i can't do the treatments i need
componentDidUpdate lifecycle hook is called after each subsequent render, as it can be seen on this diagram:
otice that it isn't called on initial render when componentDidMount is called instead after render hook.
Data that was received via props can be processed there, this requires to call setState or forceUpdate then to make it rendered.
In original code, this requires to introduce the state to this component. Also, componentWillMount was deprecated because it was commonly misused for cases like this one. It should be replaced with constructor code or componentDidMount. In this case the state synchronously derives from myarraydata prop, this is what getDerivedStateFromProps is for:
state = { processedData: null };
static getDerivedStateFromProps(props, state) {
if (conditionsToNotProcessData) return null;
const processedData = props.myarraydata || [];
for (const item of processedData) { ... }
return { ...state, processedData };
}
render() {
return (
<div>
<pre>{JSON.stringify(this.state.processedData)}</pre>
</div>
);
}
Try using the componentDidMount lifecycle method.
Need elaboration on
I have some data available only at the last time the render is called
What does this mean exactly?
EDIT : Try this :
componentWillMount() {
let data = get(this.props, 'myarraydata', [])
if(data.length !== 0){
data.forEach(element => {
//here i will do some treatments for each elemment
});
}
}

Calling a method inside getDerivedStateFromProps in React

Before upgrading react to version 16.3, I'd call a method based on changes in props like this :
componentWillReceiveProps(nextProps){
if(this.props.country.length !== nextProps.country){
doSomething(); //example calling redux action
}
}
componentWillReceiveProps is unsafe on Version 16.3 and we must use getDerivedStateFromProps. However, this method returns an object and I don't know how I can call a method from inside it the same way I do with componentWillReceiveProps
Yes, you need to return an object, which is the new state that that is derived from nextProp. According to docs:
getDerivedStateFromProps should return an object to update state, or null to indicate that the new props do not require any state updates.
But since you are not updating your state in any way inside your componentWillReceiveProps, you should use componentDidUpdate instead of getDerivedStateFromProps:
componentDidUpdate(prevProps){
if ( prevProps.country !== this.props.country.length ) {
doSomething(); //example calling redux action
}
}
For this situation, it was good for the OP to use componentDidUpdate but I found myself needing getDerivedStateFromProps so I had to make my custom function static as well and call it using the class' name inside getDerivedStateFromProps. Something like this:
componentDidMount() {
const something = ClassComponentName.runThisFunction();
this.setState({ updatedSomething: something });
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.key !== prevState.key) {
return {
updatedSomething: ClassComponentName.runThisFunction()
};
}
return null;
}
static runThisFunction() {
//do stuff and return value
}
To clarify, this is updating the component's state on load as well as when new props arrive. This definitely took me back to my typed-language days. Hope it helps!
if you need to call a function in "getDerivedStateFromProps", you can put this function in state in constructor , then get this function in "getDerivedStateFromProps" from state.
put function in state in constructor:
constructor(props){
super(props);
this.state = {
func1:this.func1.bind(this)
}
}
get function from state in getDerivedStateFromProps:
getDerivedStateFromProps(props,state){
return {
model:state.func1(props.model)
}
}

How do I call a function whenever my Apollo subscription gets triggered in React?

I have a react component which uses the subscribeToMore method on an apollo graphql query to update itself on changes. It does that just fine, but I want to call some outside function when the subscription picks up a change. I want to show some hints in the UI that things have changed, let's call our function 'onUpdate', Where do I call this onUpdate function?
My component has some code that looks like this before the render function:
subscribeToNewPoints = () => {
this.props.studentSessionPointsQuery.subscribeToMore({
document: NEW_POINT_STUDENT_SESSION_SUBSCRIPTION,
variables,
updateQuery: (previous, { subscriptionData }) => {
const newAllPoints = [
subscriptionData.data.Point.node,
...previous.allPoints
]
const result = {
...previous,
allPoints: newAllPoints
}
return result
}
})
}
componentDidMount() {
this.subscribeToNewPoints()
}
Am I supposed to put my onUpdate function inside here, or somewhere else?
Forgive me if this is answered in another question, but all the others I could find appeared to be asking how to update the cache for the query or how to get subscriptions to work in the first place.
React has it's lifecycle event ComponentWillUpdate which called right before your component gets an update.
Or You can use ComponentDidUpdate which called just after your component gets an update.
ComponentWillUpdate() {
/*called just before an update */
onUpdateFunc()
}
ComponentDidUpdate() {
/*called just after an update */
onUpdateFunc()
}

Re-render React component when prop changes

I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.

Resources