I have below piece of code
import React, { useEffect, useState } from 'react';
const Mycomp = () => {
const [firstState, setFirstState] = useState(0);
const [secondState, setSecondState] = useState(0);
const [thirdState, setThirdState] = useState([]);
useEffect(() => {
console.log(thirdState);
console.log(firstState);
console.log(secondState);
}, [thirdState])
const onClick = () => {
setThirdState(["something"])
setFirstState(1)
setSecondState(2)
}
return (
<div>
<button onClick={onClick}>Submit</button>
</div>
);
};
export default Mycomp;
I have two questions here
- Is it possible that as I have written
setThirdState
at the first inonClick
function then it gets called & set the state and call theuseEffect
and I do not get updatedfirstState
&secondState
? - Is it good to use array as dependency in
useEffect
. If not, then what is the other option?
Edit: Here is my use case
const Mycomponent = () => {
const [clipcount, setClipcount] = useState(0);
const [clipsDetails, setClipsDetails] = useState([]);
const [activePage, setActivePage] = useState(1);
const [socketClipCount, setSocketClipCount] = useState(0);
useEffect(() => {
webhookSocket.on(`livecut${id}`, (data) => {
setSocketClipCount((socketClipCount) => socketClipCount + 1);
});
}, [webhookSocket]);
useEffect(() => {
getClips();
}, [activePage])
const getClips = async () => {
const { value } = await getAllClips({
limit: clipLimit,
pageNo: activePage,
sort: { _id: -1 },
});
if (value?.success) {
setClipcount(value.totalLength);
setClipsDetails([...clipsDetails, ...value?.clips]);
}
};
return (
<div className="custom-generated-clips styled-scroll">
{socketClipCount && <button onClick={() => {
setSocketClipCount(0);
setClipsDetails([]);
setActivePage(1);
}}>
Refresh
</button>}
<InfiniteScroll
dataLength={clipsDetails?.length}
hasMore={clipsDetails?.length < clipcount ? true : false}
next={() => setActivePage(activePage + 1)}
loader={
<div className="InfinitScrollLoadingBottom">
<div className="InfinitScrollLoadingBottomText">Loading...</div>
</div>
}
endMessage={
<div className="InfinitScrollEndBottom">
<div className="InfinitScrollEndBottomText">
{clipsDetails?.length > 4 ? "You've reached End!" : ""}
</div>
</div>
}
// pullDownToRefreshThreshold={limit}
scrollableTarget="scrollableDiv"
>
{myData}
</InfiniteScroll>
</div>
)
};
export default Mycomponent;
So here I am getting clipDetails
and showing it in Infinite scroll
which is simple thing. Now when I get clips from socket I show the Refresh
button and refreshing that section by setting setSocketClipCount(0);setClipsDetails([]);setActivePage(1);
but it does not set the state for the clipDetails
& socketClipCount
array.
What could be the solution for it? I am using React 18 even batch updating is not working.
It is hard for me to get things because sometimes React official docs says
- It is not good to club the state like
useState({ state1: "", state2: "" })
- React 18 supports batch update still the above code does not work.
4
Answers
It does.
But since you are updating
activePage
and this latter is included in theuseEffect
dependency array, once the component finishes rerendering,useEffect
will run, and since the code inside it is updatingclipsDetails
andclipsDetails
this will make the component rerender again, and it looks like onlyactivePage
is updated.So if you want to run
getClips
only on the first mount of the component you have to give an empty dependency array touseEffect
:And if you want to execute
getClips()
in some specific cases, not only on the first mount, then you can create another state:then each time you want to trigger
getClips()
all you have to do is to updatetrigger
:It is not about good or bad, it’s about the use case.
If you want to trigger the code inside a
useEffect
when the component rerenders and ‘something’ is different from what it was during the last render, then you include this ‘thing’ in the dependency array, and if you wantuseEffect
to act as thecomponentdidmount
(run only when the component is mounted) than you give it an empty dependency array[]
and if you don’t give a dependency array at all, this will make the useEffect hook run on every component rerender.And if you mean an array inside the dependency array then yes you can, but keep in mind that each time the array is updated the component will rerender and useEffect will fire (the component only rerenders when the new value of useState is different from the current one) but since two objects are always not equal in javascript, even if the array is updated with the same value this will make the component rerender anyway.
the issue here is using
useEffect
for user actionsboth of your usecases are good (according to docs), but only on paper:
webhookSocket
but due to how you rely on setstate and rerenders to react to user action and for example fetch data, your flow breaks along the way. this, paired with impure functions makes it even harder to trace whats happening.
separate your events
we can break it down in two events that are user triggered, and two that are triggered either outside of react, or just happen without any action.
❌ no
useEffect
✅
useEffect
heres what i’d change, to accomplish that:
getClips()
move the implicit dependency onactivePage
to an explicitpage
param. that way you can call it whenever you want with the parameters you needgetClips()
in a run-onceuseEffect
(we should supplygetClips
as a dependency foruseEffect
, and thus wrap it inuseCallback
to make it stable)clipsDetails
, dont use a potentially stale value forclipsDetails
in the spread.setClipsDetails((prev) => [...prev, ...value?.clips]);
this makes sure it uses the right value.refresh
button: refetch data with the currently active page, and reset the "unseen clips" counter.next
event in the infinite scroll: load the next page and incrementactivePage
.i’ve added a few more smaller things as comments. i hope this helps.
i’d highly recommend reading this article in the new react docs, as this helps you prevent error-prone uses of
useEffect
If i understand your use case
socketClipCount which is missing in your code
On your refresh click you are having two similar setState (set clipDetails to empty array then getClips executes and set the clipDetails again with the http response) , one after the other and only the last one will take effect
To make your code works you have to add a new dependency (socketClipCount) to getClips effect and use it to check when the getClips function is executed
As a result when you click refresh you will have
If you find that socketClipCount is not changed it’s probably changed in the same time here with a new value different from 0
May be closing the connection for few seconds when refreshing the open it again would be a good solution
Yes, It is better to keep dependency array in useEffect.
If your onClick() function have more than one functions and you need a guarantee to to see the state changes from the firstfunction to consecutive functions, then you can use callback function. since you want first function to get executed in onClick(), what you can do is you can call the second function after setting the required state in the first function.
All about dependency array in useEffect, nice explanation is given on useEffect dependency array in React.js
See explanation by nem035 for explanation on empty dependency