Skip to content
Advertisement

React hooks in dynamic lists – more hooks than previous render

Looking for an approach that allows me to use state hooks in dynamic lists of functional components. As my code stands a re-rendering of the list gives errors concerning changing numbers of hooks.

I have a master component that renders a list of Functional components, the length of the list depends on the state held by the master, and that state may be updated by an Effect hook; the change happens asynchronously. I want to allow the list items to each have their own state – the state controls some visual effects.

When my list size changes I get errors of this kind:

react-dom.development.js:86 Warning: React has detected a change in the order of Hooks called by EstateOverview. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks

Previous render Next render

  1. useState useState
  2. undefined useState

and

Uncaught Error: Rendered more hooks than during the previous render.

The code:

the list, displaying Real Estate Properties as a list of PropertyGroup

import React from 'react';
import List from '@mui/material/List';
import PropertyGroup from "./PropertyGroup.js";

export default function EstateOverview(props) {

   return ( 
     <List>
       {props.estateOverview.propertyGroups.map((group) => PropertyGroup(group))}
     </List>
   );
}

the list item, this is much simplified, enough just to reproduce the problem

import React from 'react';

export default function PropertyGroup(props) {
    // trivial state, sufficient to cause error
    // if I remove this state the demo runs without error
    // but in my real code I have meaningful state to maintain
    const [x, setX] = React.useState(false);
    return (
        <h2 key={props.name}>{props.name}</h2>               
    );
}

The above is invoked from this Real Estate master component

import React, { useEffect, useState } from 'react';
import EstateOverview from "./EstateOverview.js";

export default function Estate(props) {

  const [estateOverview, setEstateOverview] = useState( 
      { "propertyGroups" : [
          { name: "The Close"}
         ] 
      }
   );

useEffect(() => {

const newEstateOverview =  {  
  propertyGroups: [
    { name: "The Avenue"},
    { name: "Broadway" }
   ]
  };
  // represents new data arriving asynchronously
  setTimeout(() => {
     setEstateOverview(newEstateOverview);
  }, 1000 );
});

  return (   
    <EstateOverview estateName={props.estateName}  estateOverview={estateOverview} />  
  );
}

Advertisement

Answer

The problem is in EstateOverview:

return ( 
    <List>
        {props.estateOverview.propertyGroups.map((group) => PropertyGroup(group))}
    </List>
);

This is attempting to render a React component by calling it as a function. What you should do, especially when dealing with functional components that contain hooks:

  1. Use React.createElement
  2. Use JSX to render the component (Using JSX calls React.createElement under the hood)

This is the correct way to render this:

return ( 
    <List>
        {props.estateOverview.propertyGroups.map((group) => <PropertyGroup {...group} />)}
    </List>
);
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement