I am trying to understand how I can use React Hooks in subcomponents without triggering an error.
Let's say I have a super simple component (all the code is runnable here - https://codesandbox.io/embed/xenodochial-wright-mwqlk?fontsize=14&hidenavigation=1&theme=dark):
import { useEffect, useState } from "react";
const TestComponent = (index) => {
const [a, setA] = useState("nothing");
useEffect(() => {
setA(index);
}, [setA, index]);
return <>{a}</>;
};
export default TestComponent;
And it is used like this:
import { useEffect, useState } from "react";
import Component from "./Component";
export default function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
setArr([1]);
}, []);
const component = arr.map((_, index) => Component(index));
return <div>{component}</div>;
}
Now I am getting an error:
Error
Rendered more hooks than during the previous render.
▶ 5 stack frames were collapsed.
TestComponent
/src/Component.js:4:29
1 | import { useEffect, useState } from "react";
2 |
3 | const TestComponent = (index) => {
> 4 | const [a, setA] = useState("nothing");
| ^
5 |
6 | useEffect(() => {
7 | setA(index);
There are more details in the console:
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://reactjs.org/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useState useState
2. useEffect useEffect
3. undefined useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at App (https://mwqlk.csb.app/src/App.js:24:41)
I understand the basic concept of the hooks rules that I cannot use hooks after if statements or such, they should always be on the top level.
However, this case is clearly not using any conditionals for declaring the hooks. All additional hooks are actually in a subcomponent. So why are they accounted for by React as part of the App component?
So, how can I safely use hooks in imported subcomponents then?
CodePudding user response:
You're calling your react component like a function instead of using it as a component. This causes some weirdness with rendering. If you change your .map line to
const component = arr.map((_, index) => <Component index={index} />);
and destructure your props appropriately in Component
const TestComponent = ({ index }) => {
everything works as expected.
CodePudding user response:
Here's how you should pass
App.js:
import { useEffect, useState } from "react";
import Component from "./Component";
export default function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
setArr([10,12,12,12,12]);
}, []);
return <>{arr.map((_, index) => (
<Component key={index} index={index}/>
))}</>
}
Test component:
import { useEffect, useState } from "react";
const TestComponent = ({index}) => {
const [a, setA] = useState("");
useEffect(() => {
setA(index);
}, []);
return <>{a}</>;
};
export default TestComponent;
CodePudding user response:
You should refactor in this way
Component.js
import { useEffect, useState } from "react";
const TestComponent = ({index}) => {
const [a, setA] = useState("nothing");
useEffect(() => {
setA(index);
}, [setA, index]);
return <>{a}</>;
};
export default TestComponent;
App.js
import { useEffect, useState } from "react";
import TestComponent from "./Component";
export default function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
setArr([1]);
}, []);
const component = arr.map((_, index) => <TestComponent index={index}/>);
return <div>{component}</div>;
}
