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
- useState useState
- 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} />
);
}
CodePudding user response:
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:
- Use
React.createElement
- 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>
);