Never call a React function component

francodalessio

Franco D'Alessio

Posted on May 1, 2020

Never call a React function component

When we write React code, we use JSX to create React elements. In case you don’t know, JSX is an extension to Javascript to support syntax that looks like the HTML code you would write to create DOM elements.

JSX allows us to create a React element by just writing this:

const element = <div>Hello World</div>;

As the browser does not understand JSX natively, Babel then converts the JSX syntax using react.createElement API.

If you want to know more about JSX and React elements, you can check this post.

Then we have Components

React allows us to group React elements and create components. They let you split the UI into independent, reusable pieces, and think about each piece in isolation.

Conceptually, components are like JavaScript functions. They return React elements describing what should appear on the screen.

As you may already know, a component can be written as a class

import React from "react";

export default class Component extends React.Component {  
  render() {  
    return <h1>Hello World</h1>;  
  }  
}

…or as a function:

import React from "react";

export default function Component() {  
  return <h1>Hello World</h1>  
}

But when it comes to rendering a component, you always write the same thing, both for classes and function components:

...  
return <Component />;

You render your components using JSX syntax. And this works just fine.

But if you have a function component, can’t you just call the function?

Well, you can, but weird things will happen. Let’s take a look.

Let’s start with a basic app

Our example application will consist of two components: App and Item. The App component will render a list of items, and each item is an input field. There is also a button which allows us to add a new item to the list.

import React, { useState } from "react";  

import Item from "./Item";import "./styles.css";

export default function App() {  
  const [list, setList] = useState([0]);  
  const addItem = () => setList([...list, list.length]);  

  return (  
    <div className="App">  
      <button onClick={addItem}>Add Item</button>  
      <h1>This is the list:</h1>  
      {list.map(_ => Item())}  
    </div>  
  );  
}

Please note that inside the map we use to render the list items, we are calling the Item component as a function: Item().

Our Item component is just an input field:

import React, { useState } from "react";  

import "./styles.css";

export default function Item() {  
  const text, setText = useState();  
  const handleChange = e => setText(e.target.value);  
  return <input value={text} onChange={handleChange} />;  
}

Please also note that both of our components are using the useState hook. This is important.

Here is the finished app so you can play with it:

You may have noticed there is a warning because we are not passing the key prop for our Item components.The key prop is important, but not the issue I want to talk about here.

Go ahead and click the Add Item button. You should get this error:

React complains because all renders must have the same number of Hooks calls

Ok, so we know that calling a function component produces some weird error related to Hooks, but what is it exactly? 🤔

Understanding the problem

If you check the console, you will find more information:

Warning: React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: [https://fb.me/rules-of-hooks](https://fb.me/rules-of-hooks)  

   Previous render            Next render  
   ------------------------------------------------------  
1\. useState                   useState  
2\. useState                   useState  
3\. undefined                  useState  
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  

    in App (at src/index.js:9)  
    in StrictMode (at src/index.js:8)

There are multiple rules for Hooks, and an important one is this one:

You need to make sure that the Hooks are always called the same number of times for a given component.

Do you understand the problem now?

The way we’re calling Item, is not a component at all, but a function. React cannot associate any useState calls to the Item function, just because it’s not rendered as a component.

So the useState call that should be associated to Item, is actually associated with App. Now the error message “Rendered more hooks than the previous render” finally makes sense.

This is the reason we use JSX to render our components, even if they are function components. That way, React can register any Hooks that are used in a component, with the instance of that component.

Ok, got it. But why the app works just fine if you don’t press the button?

You’ve probably noticed that when the app starts, it works just fine. You can even write in the input field and you won’t see any error.

This is because the App component is not being re-rendered. Once we click the button to add a new item, App is re-rendered and then React notices that the number of Hooks calls doesn’t match with the one of the previous render.

So there will be cases in which you won’t see an error, but doing this is still wrong. In our example, at the beginning it works, but the useState call of Item is being associated to App. This is wrong, and it can make your app behave in unexpected ways as you make changes.

That’s it!

I hope this was useful! Thanks for reading ❤️

💖 💪 🙅 🚩
francodalessio
Franco D'Alessio

Posted on May 1, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related