Introduction
Since the introduction of react-hooks in Reactv16.8, it has made the life of a react developer simpler than it has ever been with class components.
Problems with class components:
- Confusing (both humans and machines, especially at binding and this keyword).
- Lifecycle methods, logic spread over different lifecycle methods.
- Hard to test compared to functional components.
- Compiled code size and compile time.
All the above-mentioned problems are solved by react-hooks but they come up with their own set of problems.
Since design patterns and best practices are not explicitly mentioned in react hook documentation, mistakes are made by beginners during the initial phase of using hooks.
Familiarity with react hooks will help you understand the concept better and is a prerequisite for the article.
Enough of theory, Lets Begin.
1. Avoid memoization on every single component
React.memo() is a great tool to memoize functional components.
It prevents components from rendering when previous and the next props are equal.
If react memo is not use correctly it can even hamper the application performance. Use React.memo() wisely.
When you wrap any functional component with a memo, react performs two operations:
- Invokes the comparison function to determine whether the previous and next props are equal or not.
- Based props comparison react perform the diff of previous and current render results.
Since the props of the component are react nodes, it will always render, hence memo here just creates a comparison overhead before re-rendering happens.
Similarly, when props are nested objects or arrays they will always cause re-render.
So where is it useful?
Here the props passed are string and passing the same props will always return the memoized react component because the comparison is straightforward and shallow.
Protip: Avoid adding memo on small components, since re-rendering will be faster then memoizing.
2. Avoid excessive use of useCallback hook
A common misconception is that useCallback prevents "function creation during render". It doesn’t.
What useCallback does is memoize the returned function object based on the provided dependencies.
This means that given the same dependencies (compared by reference) it returns the same function object.
If the component or hook to which you’re passing this useCallback()‘ed function doesn’t care about whether or not it has been given a new function.
So now instead of just creating a new function, you’ve now:
- created a new function, created a new array of dependencies.
- invoked a function (useCallback), and caused React to compare equality of the dependencies.
- store the function and a new set of dependencies into memory.
This is more expensive than just creating and passing the new function as a prop - and often for no reason - the code would have functioned exactly the same with or without the useCallback!.
Protip: Here's an in-depth analysis of when and when not to use useCallback - https://royi-codes.now.sh/thousand-usecallbacks/
3. useRef hook has more usage than just to store DOM reference
useRef hook is used to store the DOM reference throughout the component's lifecycle
It is mutable and also does not changes on component re-render.
In class components, we have variables declared in the constructor which can be used throughout the component and will not change during re-render
If we try to achieve the same in react hooks by
count will always be 1 after each render. The problem here is that a new count variable will be created for every render.
But how can we achieve the same with hooks?
useRef hook to the rescue.
Here useRef hook is created with an initial value of 0, it is mutable and does not reset on re-render.
4. Always use hooks at the top level of your react component
Don’t call Hooks inside loops, conditions, or nested functions.
By following these rules, you ensure that hooks are always called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of hooks between multiple useState and useEffect calls.
Let’s make a Biodata component which will have two states:
- name
- age
These states will have default truthy values, we’ll make use of the useEffect hook to print the name and age values.
function Biodata() {
// 1. Use the name state variable
const [name, setName] = useState('Hello world');
// 2. Use an effect for printing the name
useEffect(() => console.log(name) );
// 3. Use the age state variable
const [age, setAge] = useState(24);
// 4. Use an effect for printing the age
useEffect(() => console.log(age) );
return (
... // logic to set name and age to falsy values
)
}
Execution Stack:
[
'hello world',
() => console.log('hello world'),
24,
() => console.log(24)
]
But what happens if we put a hooks call inside a condition?
If the order of our hooks changes (which can be possible when they are called in loops or conditionals), React will have a hard time figuring out how to preserve the state of our component.
function Biodata() {
// 1. Use the name state variable
const [name, setName] = useState('Hello world');
// 🔴 We're breaking the first rule by using a Hook in a
// condition
// 2. Use an effect for printing the name
!!name && useEffect(() => console.log(name) );
// 3. Use the age state variable
const [age, setAge] = useState(24);
// 🔴 We're breaking the first rule by using a Hook in a
// condition
// 4. Use an effect for printing the age
!!age && useEffect(() => console.log(age) );
return (
... //logic to set name and age to falsy values
)
}
The !!name
and !!age
condition is true on the first render, so we run this Hook.
However, on the next render if the user set's the name or age from input taken by the user to null or any falsy value.
Now the Execution Stack is :
[
'',
0,
]
We have skipped two useEffect hooks during rendering, the order of the Hook calls becomes different, leading to bugs.
This is why hooks must be called on the top level of our components.
If we want to run an effect conditionally, we can put that condition inside our hook.
Conclusion:
Hooks have come in as a boon for react developers, but if you don't use them correctly they can quickly become a curse.
When creating an application on a larger scale, these small guidelines help achieve a scalable application for the future.
Top comments (0)