Hooks has increasingly become a new trendy way to experience the beauty of React. This article gives you some advices on how to use Hooks properly, along with a detailed explanation and insight.
Advice #1: UseHook Appropriately
The React community has ever been so high when Hooks first came out. Hooks are cool! Hooks are trendy! Hooks is the future of React!
Some of my colleagues even attempted to migrate their project codebase to Hooksโ fantasy land. But be careful! Donโt use Hooks if you donโt understand the motivation why React team invented Hooks. Please spend at least 30 minutes reading and think deeply about it.
Here to sum up, I can give you 2 reasons why Hooks exists:
- To extract stateful logic from a component, so it can be tested independently and reused easily
- To offer an alternative toย class, because thisย is full of confusion and surprise.
I admit that Hooks have fulfilled its duty and deserve praise. I do love Hooks! But my advice is if you donโt have any particular problem with those reasons mentioned above, and if your code is already concise and effective, then there is no need to rewrite all your components with React Hooks, unless you truly want to experiment with the hype.
Advice #2: React Hooks Will Not Replace Redux
Hooks are Great, but No.
Eric Elliott wrote an excellent article here to explain why Hooks is not an absolute replacement of Redux.
Hooks is a set of APIs (or functions) that helps you play with state and other React features. Meanwhile, Redux is an architecture that you use to organize your code and control the flow in a predictable manner.
When talking about Redux, we not just mention about a state management library, we mention about a huge Redux ecosystem with plenty of open source libraries and powerful tools to help you deal with side effects, immutability, persistence, โฆ
Moreover, you can use Redux with Hooks together. So instead of reinventing the wheel, you can use both to achieve a greater good.
Advice #3: Avoid State Mutation
Similar toย this.setStateย in class component, you should not modify state directly:
const [user, setUser] = useState({name: "Jane"})// wrong
user.name = "John"// will not re-render
// because the reference of user is still the same
setUser(user)
Instead, always create a new state:
const [user, setUser] = useState({name: "Jane"})// will trigger re-render
setUser({...user, name: "Jane"})
Advice #4: Understand How Dependencies Array Works
Many React developers are used to component lifecycle, especially with componentDidMountย andย componentDidUpdate. So when learning Hooks, their first question usually is: โHow to run this piece of code just one?โ.
In other words, how to makeย useEffectย run only one time afterย renderย (likeย componentDidMount). And the answer isย dependencies array.
Itโs the array you pass as the second argument to useEffect:
useEffect(() => {}), depArr)
Dependencies array is a list of values that are used in shallow comparison to determine when to re-run the effect. We can imagine a pseudo code like so:
// this aims to illustrate the idea.
// not the exact implementation of useEffect.
// the actual comparison algorithm React uses is Object.islastDeps = nulluseEffect = (func, deps) => {
if (!deps)
return func()
if (!lastDeps) {
lastDeps = [...deps] // shallow copy
return func()
}
for (i = 0; i < deps.length; ++i)
if (lastDeps[i] !== deps[i]) // shallow compare
return func()
}
Look at this pseudo implementation ofย useEffect, you can better understand why and when aย useEffectย will re-run in the following code:
// run every time after render
useEffect(() => {
console.log('run effect')
})// run only one time after first render
useEffect(() => {
console.log('run effect')
}, [])
In the code below,ย useEffectย performs a referential equality check, so it cannot determine if any properties ofย userย have changed or not.
const [user, setUser] = useState({name: "Jane"})// run after first render, and re-run when user changed
useEffect(() => {
console.log('run effect') // perform effect here
}, [user])...// press the button somewhere
onPress = () => {
setUser({name: "Jane"})
}
Althoughย nameย maintains the same value โJaneโ, this will trigger a re-render and re-rerun the effect function.
Advice #5: Understand useCallback and useMemo
- If you pass a function to a child component as callback, you should memoize that function with useCallback.
- If you have data that is expensive to compute, you should memoize it with useMemo.
Example: Generate and display a random number.
Withoutย useCallbackย :
- anytime you clickย RandomButtonย , it callsย setNumberย , which triggers re-render onย Containerย (see log)
- whenย
Containerย re-renders, it initiates a newยrandomizeย function, then pass this function down toยRandomButtonย as prop. RandomButtonย receives a new prop and decides to re-render (see log).
const Container = () => {
const [number, setNumber] = useState(0)
const randomize = () => setNumber(Math.random()) console.log("render Container")
return (
<>
{number}
<RandomButton randomize={randomize}/> ) }const RandomButton = ({randomize}) => { console.log(“render RandomButton”) returnrandomize}/> }
Now, withย useCallbackย andย React.Memo:
- whenย
Containerย re-renders,ยuseCallbackย checks its dependencies array to determine whether to return the last memoized function or create a new one. - but in this case, we provide an empty array, which meansย useCallbackย will always return the sameย randomizeย function.
- when passingย randomizeย function as prop toย RandomButton, with the help ofย React.Memo, it checks and sees thatย randomizeย prop is the same, so no need to re-render (see log).
const Container = () => {
const [number, setNumber] = useState(0)
const randomize = useCallback(() => setNumber(Math.random()), [])
console.log("render Container")
return (
<>
{number}
<RandomButton randomize={randomize}/> ) }const RandomButton = React.memo(({randomize}) => { console.log(“render RandomButton”) returnrandomize}/> })
In this special occasion, we pass an empty array toย useCallbackย because its memoized functionย () => setNumber(Math.random())ย doesnโt need any extra information to execute.
But what if we want to useย numberย state in the randomizeย function, like so:
() => setNumber(number + Math.random())
In this situation,ย randomizeย depends onย numberย state to execute correctly. So we specifyย numberย as an dependency inย useCallback:
const randomize = useCallback(
() => setNumber(number + Math.random()), [number]
)
But still, this may not be a perfect solution because whenย randomizeย callsย setNumber, it will updateย numberย with a new value, thenย useCallbackย checks that its dependencies have changed and create a newย randomizeย function, resulting in an unnecessary re-render ofย RandomButtonย again.
In order to deal with this dilemma, we can use functional updates. This is especially useful when the new state is computed using the previous state.
A perfect solution is as follow:
const randomize = useCallback(
() => setNumber(number + Math.random()), [number]
)
Source:ย https://medium.com/@blueish/5-advices-when-using-react-hooks-e476bbd160c4