In this blog I would like to demystify React’s functional components. I want to give you a deeper understanding of why React works as it does, so that you can focus on writing React applications instead of hunting bugs.
Function components – the mental model gap
In the “Main Concepts” section in the React docs they tell you what a function component is with a simple example.
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
In the React docs, Main Concepts in the section Function and Class Components they say:
This function is a valid React component because it accepts a single “props” (which stands for properties) object argument with data and returns a React element. We call such components “function components” because they are literally JavaScript functions.
React docs – Main Comcepts – Function and Class Components
With this statement they build a mental model of React that has a gap. The function you see above is not a component, it is simply a function. I should better say: “is not a component yet”. It will become a component when you use it in JSX, e.g. <Welcome/>
. But then it is more then a function, because it will get state. You should keep this in mind to prevent yourself from making mistakes as we will see when it comes to React Hooks.
So what will happen if you invoke the Example function directly.
const example = Example()
You will then get the error:
Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
The statement “can only be called inside of the body of a function component” makes clear that the Example
function is not the functional component. Otherwise the error would not make sense.
So what makes Example
a functional component? A functional component is something that is connected to React. You usually make it a funtional component by using it in JSX.
<Example/>
You can see the difference when you take a look at the transformed code that the JSX transformer produces. You will see something like this:
React.createElement(Example, {}, null)
As you can see the Example
function becomes a component when it is wrapped by React.createElement
what it makes it a ReactElement
. And this makes the big difference, because the ReactElement
connects the simple Example
function to React. It is this wrapper that restores the component’s state before calling the Example
function so that all the hooks like useState
, useMemo
, useCallback
, etc. will work. Otherwise the Example
function is not connected and therefore the component’s state is not available what will lead to the error I showed above.
Understanding React Hooks
The effects of the mental model gap can be severe when it comes to the component state and the mental model gap will grow. React hooks can only be understood if you are aware of the ReactElement
wrapper and how it augments your function to make it a real functional component. So let’s take a look at a hooks example from the React docs – Introducing Hooks.
import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
The example looks nice and condensed, but even more questions arise and the mental model gap grows:
Example
calls the functionuseState
anduseState
returns the state.
But sinceuseState
is just a function how can it remember the previous value – it’s state?- There might be multiple functional components that all call the same
useState
function.
How is the state separated between different components?
You can only answer these questions if you know how the wrapper function that React.createElement
creates works.
Whenever the ReactElement
is called it restores the components state before it calls the function that implmenets the component.
Also take a look at my stackoverflow answers related to functional components: