Reputation: 2953
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?
Upvotes: 0
Views: 398
Reputation: 4410
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>;
}
Upvotes: 1
Reputation: 1456
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;
Upvotes: 1
Reputation: 5766
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.
Upvotes: 2