Reputation: 1262
I run into two challenges:
see below code sample:
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: props.count > 100 ? 100 : props.count,
}
}
/*What is the equivalent implementation when React Hook is used here componentWillReceiveProps*/
componentWillReceiveProps(nextProps) {
if (nextProps.count !== this.props.count) {
this.setState({
count: nextProps.count > 100 ? 100 : nextProps.count
});
}
}
render() {
return ( <
div > {
this.state.count
} <
/div>
);
}
}
export default App;
As for componentDidUpdate, componentDidUpdate has its counterpart when React Hook is used, you have to use it like,
React.useEffect(() => {
return () => {
};
}, [parentProp]);
the Second param for useEffect makes sure code is executed only when prop changes, but what if I want to do respective tasks based on multiple respective props changes? how to get it done with useEffect?
see below code sample:
class App extends Component {
/*What is the equivalent implementation when functional component with React Hook is used here */
componentDidUpdate(prevProps, prevState) {
if (prevProps.groupName !== this.props.groupName) {
console.log('Let'
's say, I do want to do some task here only when groupName differs');
} else if (prevProps.companyName !== this.props.companyName) {
console.log('Let'
's say, I do want to do some different task here only when companyName differs');
}
}
render() {
/*for simplicity, render code is ignored*/
return null;
}
}
export default App;
Upvotes: 66
Views: 100878
Reputation: 1307
I'm using React Native. I added this to my screen component where I needed a componentWillReceiveProps functionality:
function MyComponent({ props }) {
const propsRef = useRef(props)
const componentWillReceiveProps = () => {
const propsHaveChanged = !isEqual(propsRef.current, props)
propsRef.current = props
if (propsHaveChanged) {
// Do stuff after props has changed
}
}
componentWillReceiveProps();
return (
<View>
<Text>My Component</Text>
</View>
)
}
Here isEqual
is a lodash function that does a deep comparison. Then just call the function.
componentWillReceiveProps()
Notice that I called componentWillReceiveProps at the top level inside the functional component. So, as a result this function will be called before the component renders. The ref that I use will -preserve it's data across renders. So, when componentWillReceiveProps runs on every render it will look at the props that are being passed in and compare that to the props from the previous render which are stored in the ref. Then you check if they are different so you can perform whatever actions you need based on the props changing. Next you set the ref to the current props to check against on the next render. Hope that helps. 🍻
Upvotes: 0
Reputation: 2341
In a case, you want to replicate a log system such as why did you render, and you need the nextProps
here is helping hook.
const useComponentWillReceiveProps(nextProps, callback) {
const props = useRef(nextProps)
useEffect(() => {
callback(nextProps, props.current)
props.current = nextProps
})
}
const diffLogger = (nextProps, props, { name = "Component" } = {}) => {
Object.keys(nextProps)
.filter((key) => nextProps[key] !== props[key])
.forEach((key) => {
console.log(
`%c${name}:%c${key}`,
"color:#ff5722; font-size:1rem; font-weight:bold;",
"color:#ffc107; font-size:1.2rem; font-weight:bold;",
{
from: props[key],
to: nextProps[key],
}
)
})
}
const Button = (props) => {
useComponentWillReceiveProps(props, diffLogger, {name: 'Button'})
return <button onClick={props.onClick}>Button</button>
}
Now If your button re-render e.g. by onClick
new reference you will get something like this:
Upvotes: 1
Reputation: 490
setCount
will trigger a re-render. Using useEffect
with [count]
as the dependencies array will ensure that the hook will only calls setCount
when count
's value changes.
This is how you go about replacing whatever componentWillReceiveProps
logic you might have otherwise written in the old class-based React style. I find the "Each Render Has Its Own Props and State" principle useful: if you want to trigger a re-render only when specific props change, you can have several useEffect
hooks.
useEffect(() => {
count > 100 ? setCount(100) : setCount(count)
}, [count])
useEffect(() => {
console.log('groupName has changed');
// do something with groupName
}, [groupName])
useEffect(() => {
console.log('companyName has changed');
// do something with companyName
}, [companyName])
Upvotes: 7
Reputation: 1442
simply by using useEffect like this.
useEffect( () => {
props.actions.fetchSinglePost(props.match.params.id); //> I'm dispatching an action here.
}, [props.comments]) //> and here to watch comments and call the action in case there is any change.
Upvotes: 2
Reputation: 1539
If you use the useMemo hook on top of your component and have it dependent on all your props, it runs before everything everytime props change. useEffect is triggered after the updated render and since dependent on all props it triggers after a rerender depending on all props.
const Component = (...props) => {
// useState, useReducer if have
useMemo(() => {
// componentWillReceiveProps
},[...props]);
// ...other logic and stuff
useEffect(() => {
// componentDidUpdate
}, [...props]);
};
Upvotes: 11
Reputation: 3877
The react hook equivalent to the old componentWillReceive
props can be done using the useEffect
hook, just specifying the prop that we want to listen for changes in the dependency array.
I.e:
export default (props) => {
useEffect( () => {
console.log('counter updated');
}, [props.counter])
return <div>Hi {props.counter}</div>
}
For componentDidUpdate
just by omitting the dependency array, the useEffect
function will be called after every re-render.
I.e:
export default (props) => {
useEffect( () => {
console.log('counter updated');
})
return <div>Hi {props.counter}</div>
}
Upvotes: 47
Reputation: 74500
1.) What is the equivalent implementation with React Hook, If I do need derived state?
Derived state for Hooks = set state conditionally and directly in the render phase:
constComp = (props) => {
const [derivedState, setDerivedState] = useState(42);
if (someCondition) {
setDerivedState(...);
}
// ...
}
This updates state without an additional commit phase as opposed to useEffect
. Above pattern is supported by React Strict Mode (no warnings):
const App = () => {
const [row, setRow] = React.useState(1);
React.useEffect(() => {
setTimeout(() => {
setRow(2);
}, 3000);
}, []);
return (
<React.StrictMode>
<Comp row={row} />
</React.StrictMode>
);
}
const Comp = ({ row }) => {
const [isScrollingDown, setIsScrollingDown] = React.useState(false);
const [prevRow, setPrevRow] = React.useState(null);
console.log("render, prevRow:", prevRow)
if (row !== prevRow) {
console.log("derive state");
// Row changed since last render. Update isScrollingDown.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js" integrity="sha256-4gJGEx/zXAxofkLPGXiU2IJHqSOmYV33Ru0zw0TeJ30=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.min.js" integrity="sha256-9xBa2Hcuh2S3iew36CzJawq7T9iviOAcAVz3bw8h3Lo=" crossorigin="anonymous"></script>
<div id="root"></div>
Note 1: componentWillReceiveProps
has been deprecated for quite some time. getDerivedStateFromProps
is the class components' successor in terms of derived state.
Note 2: Check preferred solutions before you resort to derived state.
2.) What if I want to do respective tasks based on multiple respective props changes?
You can either leave useEffect
deps out completely or preferably add another prop dep:
React.useEffect(() => {
return () => { };
}, [parentProp, secondProp]);
Upvotes: 0
Reputation: 57
In your scenario, you don't need to use or re-implement getDerivedStateFromProps
at all. You just need to create a new variable to get the new form of data. Using state in this scenario will just cause another re-rendering which is not good performance wise.
import React from 'react';
const App = ({ count }) => {
const derivedCount = count > 100 ? 100 : count;
return (
<div>Counter: {derivedCount}</div>
);
}
App.propTypes = {
count: PropTypes.number.isRequired
}
Demo here: https://codesandbox.io/embed/qzn8y9y24j?fontsize=14
You can read more on different ways to solve these kind of scenarios without using getDerivedStateFromProps
here: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
If you really need to use a separate state, you can use something like this
import React, { useState } from 'react';
const App = ({ count }) => {
const [derivedCounter, setDerivedCounter] = useState(
count > 100 ? 100 : count
);
useEffect(() => {
setDerivedCounter(count > 100 ? 100 : count);
}, [count]); // this line will tell react only trigger if count was changed
return <div>Counter: {derivedCounter}</div>;
};
Upvotes: 2
Reputation: 81006
I realize your "derived state" example is intentionally simple, but because there are so few legitimate cases of derived state, it is difficult to make a recommendation on the replacement except on a case-by-case basis since it depends on the reason you are using derived state. In the particular example you provided, there was no reason to use derived state in the class case and so there continues to be no reason in the hook case (the value can just be derived locally without putting it in state). If the derived value is expensive, you can use useMemo
as Tholle presents. If these don't fit the more realistic case(s) you have in mind, you would need to present a more specific case that truly requires derived state.
As far as your componentDidUpdate
example, if what you want to do for the different props is independent, then you can use separate effects for each (i.e. multiple useEffect
calls). If you want to do exactly what is in your example (i.e. only do something for a companyName
change if groupName
didn't also change as indicated by your else if
), then you can use refs for more sophisticated conditions. You should not mutate the ref during rendering (there is always the possibility of the render being discarded/redone once concurrent mode is supported), so the example uses the last effect to make updates to the refs. In my example, I use a ref to avoid doing effect work on the initial render (see Tholle's answer in this related question) and to detect whether or not groupName
changed when deciding whether or not to do work based on a companyName
change.
const { useState, useEffect, useRef } = React;
const DerivedStateFromProps = ({ count }) => {
const derivedCount = count > 100 ? 100 : count;
return (
<div>
Derived from {count}: {derivedCount}{" "}
</div>
);
};
const ComponentDidUpdate = ({ groupName, companyName }) => {
const initialRender = useRef(true);
const lastGroupName = useRef(groupName);
useEffect(
() => {
if (!initialRender.current) {
console.log("Do something when groupName changes", groupName);
}
},
[groupName]
);
useEffect(
() => {
if (!initialRender.current) {
console.log("Do something when companyName changes", companyName);
}
},
[companyName]
);
useEffect(
() => {
if (!initialRender.current && groupName === lastGroupName.current)
console.log(
"Do something when companyName changes only if groupName didn't also change",
companyName
);
},
[companyName]
);
useEffect(
() => {
// This effect is last so that these refs can be read accurately in all the other effects.
initialRender.current = false;
lastGroupName.current = groupName;
},
[groupName]
);
return null;
};
function App() {
const [count, setCount] = useState(98);
const [groupName, setGroupName] = useState("initial groupName");
const [companyName, setCompanyName] = useState("initial companyName");
return (
<div>
<div>
<DerivedStateFromProps count={count} />
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment Count
</button>
</div>
<div>
<ComponentDidUpdate groupName={groupName} companyName={companyName} />
groupName:{" "}
<input
type="text"
value={groupName}
onChange={event => setGroupName(event.target.value)}
/>
<br />
companyName:{" "}
<input
type="text"
value={companyName}
onChange={event => setCompanyName(event.target.value)}
/>
<br />
change both{" "}
<input
type="text"
onChange={event => {
const suffix = event.target.value;
setGroupName(prev => prev + suffix);
setCompanyName(prev => prev + suffix);
}}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
Upvotes: 0
Reputation: 112787
You can use the useMemo
hook to store a calculation and put props.count
in the array given as second argument to recalculate the value when it changes.
const { useState, useEffect, useMemo } = React;
function App() {
const [count, setCount] = useState(50);
useEffect(() => {
setTimeout(() => {
setCount(150);
}, 2000);
}, []);
return <DisplayCount count={count} />;
}
function DisplayCount(props) {
const count = useMemo(() => props.count > 100 ? 100 : props.count, [props.count]);
return <div> {count} </div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The easiest way to do separate effects when separate props change is to create multiple useEffect
hooks that are only run when one of the separate props change.
const { useState, useEffect } = React;
function App() {
const [groupName, setGroupName] = useState('foo');
const [companyName, setCompanyName] = useState('foo');
useEffect(() => {
setTimeout(() => {
setGroupName('bar');
}, 1000);
setTimeout(() => {
setCompanyName('bar');
}, 2000);
}, []);
return <DisplayGroupCompany groupName={groupName} companyName={companyName} />;
}
function DisplayGroupCompany(props) {
useEffect(() => {
console.log("Let's say, I do want to do some task here only when groupName differs");
}, [props.groupName])
useEffect(() => {
console.log("Let's say,I do want to do some different task here only when companyName differs");
}, [props.companyName])
return <div> {props.groupName} - {props.companyName} </div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 9