Hello, everyone!đź‘‹
While coding a small component yesterday, what initially would just be a simple useState()
turned into an interesting discover about React components creation.
My intention was just to render a component by calling it as a function, since it seemed easier in my particular case. However, React wanted me to tell otherwise! And I got a Error: Rendered more hooks than during the previous render.
After doing a little research, I found out that I simply needed to change how I created the component to use it with the component syntax <Component />
instead of Component()
.
So, the TLDR is:
- Don’t call Hooks from regular JavaScript functions, only from React function components.
- Use the component as
<Component />
instead of calling it like a functionComponent()
Okay, if you were here to grab the tip you are free to go! But if you want to understand a little bit more about why it is like this then keep on reading :)
1. Only Call Hooks from React Functions
This is one of the two rules React advise/requer us to follow when using Hooks. You can read more about the rules in the docs, but this rule states that we can either:
- Call Hooks from React function components.
- Call Hooks from custom Hooks.
And the tip in this post resolves around this first option. When we create a component not using the <Component />
syntax, we are not calling it as a React function but as a normal Javascript function instead. But, what is the difference?
When we call a component like Component()
, we are not creating any React Element and simply returning the JSX.Element from the function call(after doing the whole logic before this).
When we create a component like <Component />
, we are calling React.createElement(Component, {})
which actually creates a React Element.
And that is the key. But to understand it we should know about another concept before, the React Fiber. The React Fiber architecture is a reimplementation of React's core algorithm to deal with reconciliation and re-rendering. A fiber represents a unit of work and its structure, in simple terms, is a JavaScript object that contains information about a component, its input, and its output. To learn more about you can check this README.
So when we call Component()
, the state/useState is associated with the parent fiber since we're not creating a React Element. However, when we use <Component />
, React.createElement
will be called creating a new fiber (the component itself) the state/useState will be associated with.
Calling Component()
is like directly putting the whole body and return of the function into the parent component, inlining it to the parent. Then its state and useState() is associated with the parent since Component is not being rendered as a component. At least it was how I understood. That alone is not a problem, but let's consider the example below to understand how it can lead to a Rendered more hooks than during the previous render.
error.
import React from "react"
const Counter = () => {
const [counter, setCounter] = React.useState(0)
return (
<div>
<div>This is my counter: {counter}</div>
<button onClick={() => setCounter(counter+1)}>Increment</button>
</div>
)
}
const App = () => {
return (
<div className="App">
<Counter />
</div>
)
}
export default App
Just a simple App
component with Counter
component in it. The Counter
has a useState()
to handle its own counter value and its update when the user clicks the button. In this simple case, in practice, there would be no difference between using <Counter />
or Counter()
. However, if we look into the React Devtools to check what React is doing we see a difference when changing from one way to another.
When using <Counter />
, React actually creates an Element inside the App
component but when we call Counter()
it doesn't create. Then, in the first case, the Counter
information is associated with its component but in the second it is associated with the App
component. Which hasn't been a problem so far... But let's add a button that will show or hide the Counter
component:
javascript
... Same code as before but changing the App component:
export default function App() {
const [show, setShow] = React.useState(false)
return (
<div className="App">
<button onClick={() => setShow(prevShow => !prevShow)}>Toggle show</button>
{ show && <Counter />}
</div>
);
}
If we use it like this it's okay! However, if we create the Counter calling as Counter()
, when we click the button to show the component we get:
And looking at the console from the browser:
What does this "order of the Hooks" mean and why its important?
2. Order of the Hook calls
There is another rule when using Hooks: Only Call Hooks at the Top Level. Taken from the docs:
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are 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.
So, we need to understand that React only works with multiple State and Effect hooks in a single component because it relies on the fact that the order of the Hook calls should always be the same in every render.
And, as the console error shows, that is NOT what is happening in our example. The order of the hook calls in the App
component is changing somehow. From the console, we see that in the previous render the hook called was only one useState
, then after turning show
to true
, in the next render, there were actually two useState
hooks being called. The first useState
was the one handling the show
in the App
and the second was the one handling the counter
from its child component.
As we didn't create a React Element, calling Counter()
it's just like directly putting the whole body of the Counter component inside its parent and associating its state/useState() to the App
component. This way, it added a useState
call(from the Counter) to App
and this changed its order since it didn't have this second hook before. That's why we have this error. Or at least, what I understood what caused this error.
To fix that, we could simply create the Counter component with <Counter />
. From that, we take that instead of calling a functional component we'd better use the component syntax to render it. This will prevent us from having unexpected behaviours.
3. Conclusion
Instantiating a functional component either calling the function, Component()
, or using the component syntax, <Component />
, will produce the same result in the end. However, for some cases, such as using Hooks inside the component, it's important to know the difference to how React generates the JSX.Element the component returns.
Of course, you can call the component like a function but keep in mind that when using Hooks you should use it with the Component syntax to avoid any unexpected behaviours or the Rendered more hooks than during the previous render.
error.
Also, remember that this article was written based on what I understood from what I found in the internet but it may not be 100% accurate! Since I didn't find many articles about this, I just wanted to share my understanding in case it helps. So if you know more about this and can add to the discussion feel free to leave a comment. Was the understanding behind this article correct? What are your thoughts? :)
4. References
This article would not be possible without other articles from awesome developers out there. If you want check what helped my learning, click on the links below:
https://kentcdodds.com/blog/dont-call-a-react-function-component
https://stackoverflow.com/questions/46965309/react-functional-component-calling-as-function-vs-as-component
https://medium.com/@jonchurch/how-to-fix-react-error-rendered-fewer-hooks-than-expected-e6a378985d3c
https://reactjs.org/docs/hooks-rules.html
Top comments (1)
what about in the child component does not have any hook? it will be safe right?