admin管理员组文章数量:1129797
Lets say I have some state that is dependent on some other state (eg when A changes I want B to change).
Is it appropriate to create a hook that observes A and sets B inside the useEffect hook?
Will the effects cascade such that, when I click the button, the first effect will fire, causing b to change, causing the second effect to fire, before the next render? Are there any performance downsides to structuring code like this?
let MyComponent = props => {
let [a, setA] = useState(1)
let [b, setB] = useState(2)
useEffect(
() => {
if (/*some stuff is true*/) {
setB(3)
}
},
[a],
)
useEffect(
() => {
// do some stuff
},
[b],
)
return (
<button
onClick={() => {
setA(5)
}}
>
click me
</button>
)
}
Lets say I have some state that is dependent on some other state (eg when A changes I want B to change).
Is it appropriate to create a hook that observes A and sets B inside the useEffect hook?
Will the effects cascade such that, when I click the button, the first effect will fire, causing b to change, causing the second effect to fire, before the next render? Are there any performance downsides to structuring code like this?
let MyComponent = props => {
let [a, setA] = useState(1)
let [b, setB] = useState(2)
useEffect(
() => {
if (/*some stuff is true*/) {
setB(3)
}
},
[a],
)
useEffect(
() => {
// do some stuff
},
[b],
)
return (
<button
onClick={() => {
setA(5)
}}
>
click me
</button>
)
}
Share
Improve this question
edited Jul 23, 2019 at 15:44
Bogdan D
5,5912 gold badges34 silver badges32 bronze badges
asked Dec 10, 2018 at 23:53
Dan RuswickDan Ruswick
3,1402 gold badges14 silver badges15 bronze badges
6 Answers
Reset to default 243Generally speaking, using setState
inside useEffect
will create an infinite loop that most likely you don't want to cause. There are a couple of exceptions to that rule which I will get into later.
useEffect
is called after each render and when setState
is used inside of it, it will cause the component to re-render which will call useEffect
and so on and so on.
One of the popular cases that using useState
inside of useEffect
will not cause an infinite loop is when you pass an empty array as a second argument to useEffect
like useEffect(() => {....}, [])
which means that the effect function should be called once: after the first mount/render only. This is used widely when you're doing data fetching in a component and you want to save the request data in the component's state.
For future purposes, this may help too:
It's ok to use setState in useEffect
you just need to have attention as described already to not create a loop.
But it's not the only problem that may occur. See below:
Imagine that you have a component Comp
that receives props
from parent and according to a props
change you want to set Comp
's state. For some reason, you need to change for each prop in a different useEffect
:
DO NOT DO THIS
useEffect(() => {
setState({ ...state, a: props.a });
}, [props.a]);
useEffect(() => {
setState({ ...state, b: props.b });
}, [props.b]);
It may never change the state of a as you can see in this example: https://codesandbox.io/s/confident-lederberg-dtx7w
The reason why this happen in this example it's because both useEffects run in the same react cycle when you change both prop.a
and prop.b
so the value of {...state}
when you do setState
are exactly the same in both useEffect
because they are in the same context. When you run the second setState
it will replace the first setState
.
DO THIS INSTEAD
The solution for this problem is basically call setState
like this:
useEffect(() => {
setState(state => ({ ...state, a: props.a }));
}, [props.a]);
useEffect(() => {
setState(state => ({ ...state, b: props.b }));
}, [props.b]);
Check the solution here: https://codesandbox.io/s/mutable-surf-nynlx
Now, you always receive the most updated and correct value of the state when you proceed with the setState
.
Effects are always executed after the render phase is completed even if you setState inside the one effect, another effect will read the updated state and take action on it only after the render phase.
Having said that its probably better to take both actions in the same effect unless there is a possibility that b
can change due to reasons other than changing a
in which case too you would want to execute the same logic
useEffect
can hook on a certain prop or state. so, the thing you need to do to avoid infinite loop hook is binding some variable or state to effect
For Example:
useEffect(myeffectCallback, [])
above effect will fire only once the component has rendered. this is similar to componentDidMount
lifecycle
const [something, setSomething] = useState(0)
const [myState, setMyState] = useState(0)
useEffect(() => {
setSomething(0)
}, [myState])
above effect will fire only my state has changed this is similar to componentDidUpdate
except not every changing state will fire it.
You can read more detail though this link
▶ 1. Can I set state inside a useEffect hook?
In principle, you can set state freely where you need it - including inside useEffect
and even during rendering. Just make sure to avoid infinite loops by settting Hook deps
properly and/or state conditionally.
▶ 2. Lets say I have some state that is dependent on some other state. Is it appropriate to create a hook that observes A and sets B inside the useEffect hook?
You just described the classic use case for useReducer
:
useReducer
is usually preferable touseState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. (React docs)When setting a state variable depends on the current value of another state variable, you might want to try replacing them both with
useReducer
. [...] When you find yourself writingsetSomething(something => ...)
, it’s a good time to consider using a reducer instead. (Dan Abramov, Overreacted blog)
let MyComponent = () => {
let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 });
useEffect(() => {
console.log("Some effect with B");
}, [state.b]);
return (
<div>
<p>A: {state.a}, B: {state.b}</p>
<button onClick={() => dispatch({ type: "SET_A", payload: 5 })}>
Set A to 5 and Check B
</button>
<button onClick={() => dispatch({ type: "INCREMENT_B" })}>
Increment B
</button>
</div>
);
};
// B depends on A. If B >= A, then reset B to 1.
function reducer(state, { type, payload }) {
const someCondition = state.b >= state.a;
if (type === "SET_A")
return someCondition ? { a: payload, b: 1 } : { ...state, a: payload };
else if (type === "INCREMENT_B") return { ...state, b: state.b + 1 };
return state;
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect } = React</script>
▶ 3. Will the effects cascade such that, when I click the button, the first effect will fire, causing b to change, causing the second effect to fire, before the next render?
useEffect
always runs after the render is committed and DOM changes are applied. The first effect fires, changes b
and causes a re-render. After this render has completed, second effect will run due to b
changes.
let MyComponent = props => {
console.log("render");
let [a, setA] = useState(1);
let [b, setB] = useState(2);
let isFirstRender = useRef(true);
useEffect(() => {
console.log("useEffect a, value:", a);
if (isFirstRender.current) isFirstRender.current = false;
else setB(3);
return () => {
console.log("unmount useEffect a, value:", a);
};
}, [a]);
useEffect(() => {
console.log("useEffect b, value:", b);
return () => {
console.log("unmount useEffect b, value:", b);
};
}, [b]);
return (
<div>
<p>a: {a}, b: {b}</p>
<button
onClick={() => {
console.log("Clicked!");
setA(5);
}}
>
click me
</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
▶ 4. Are there any performance downsides to structuring code like this?
Yes. By wrapping the state change of b
in a separate useEffect
for a
, the browser has an additional layout/paint phase - these effects are potentially visible for the user. If there is no way you want give useReducer
a try, you could change b
state together with a
directly:
let MyComponent = () => {
console.log("render");
let [a, setA] = useState(1);
let [b, setB] = useState(2);
useEffect(() => {
console.log("useEffect b, value:", b);
return () => {
console.log("unmount useEffect b, value:", b);
};
}, [b]);
const handleClick = () => {
console.log("Clicked!");
setA(5);
b >= 5 ? setB(1) : setB(b + 1);
};
return (
<div>
<p>
a: {a}, b: {b}
</p>
<button onClick={handleClick}>click me</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Try wrapping the setState inside an if-statement that checks whether the state needs to be changed - if yes, change it, else return () => {}
e.g.,
useEffect(() => {
if(a.currentCondition !== a.desiredCondition) {
setA();
}
return cleanup;
}, [b])
本文标签: javascriptCan I set state inside a useEffect hookStack Overflow
版权声明:本文标题:javascript - Can I set state inside a useEffect hook - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736737563a1950331.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论