Render Functions & React Components
Ensure React Function Components follow proper usage rules and are called with JSX syntax, not as regular JS functions, to avoid issues with hooks and the React Compiler.
When writing React code, people take for granted that any function that returns JSX is a valid React Component, but in reality this is not always true.
In this post when we say React Component, we’re strictly talking about Function Components and not Class Components.
Before diving deep in the difference between functions that return React nodes and React Components, let's first review what makes a React Component.
React Components are regular JavasScript functions, except that:
Typically they have a name that starts with an uppercase letter and they must be called with JSX syntax.
They return some JSX which describes the UI.
Their params are called Props and they should not be mutated.
They can be stateful, using hooks.
They must be pure, meaning that for the same input, we should always have the same exact output.
It's important to have these rules in mind, they'll save you so many hours of debugging React applications.
Often times we need to map a list of elements and render each one of them on the screen:
We mapped over each element of the fruits array and we rendered each one of them, pretty simple.
And this is how we would use this Fruits component.
From the App component we do not have much insight on how is the Fruits component rendering the list on the screen, we just know that it does render it. Fruits might be doing some perf optimizations like virtualization, memoization, whatever, you name it.
If we want to have some control over how the Fruits component renders elements on the screen, we can refactor it so that it accepts a function that will be called with each element as argument.
Now, everywhere we use the Fruits component, we need to pass it data and renderFruit as props like so:
As we can see, renderFruit is a function that returns some JSX and it will be rendered on the screen... but waiiiiiiit, is it a React Component?... Well..
If we play by the rules we've just seen earlier, no, it is not a React Component and this is where things start to get interesting.
Because loads of React codebase have an eslint-rule that warns them whenever they use inline JS function inside React components, and since renderFruit returns some JSX what most devs would do is turn it into a React Component like so:
And refactor the Fruits component accordingly:
Well, while this might even work... we're breaking the first rule we've seen because the renderFruit function inside Fruits is being called as regular JS function but we're passing in a React Component.
If we start adding stateful logic to RenderFruit(the component) and start adding more fruits to the array passed to Fruits, things will start to break.
Enabling the React Compiler
A great example of where this would break right away is by enabling the compiler in our app. Let's see.
Just for recap, here's out `RenderFruit` component, nothing has changed:
Now here's the same component optimized by the React Compiler:
Now let's only focus on the first line, the most import one.
*_c* is a React hook function even though it does not start with use... And what it does is basically create a state which allocates an array of given size in memory.
If we run our app with the compiler enabled, we'll get the following error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
We're calling hooks inside a function component which is correct, our RenderFruit is a valid React Component but the way it's being used is incorrect.
Fixing the bug
Fixing this error is quite simple, there are two ways to fix it.
The first fix would be to pass a render function to renderFruit instead of a React component, this is a plain JavaScript function, we can’t use hooks inside it, it receives plain JS values as arguments rather than props
The second fix is basically refactoring the Fruits to accept a React component which accepts fruit as props and thus we can use hooks and we call it using JSX syntax.
To summarize, ensure React Components are used correctly with JSX syntax, not regular JS function calls, especially when using hooks. Incorrect usage can break when enabling the React Compiler, leading to invalid hook call errors. Always call components using JSX.