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
setThirdState at the first in onClick function then it gets called & set the state and call the useEffect and I do not get updated firstState & secondState?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
useState({ state1: "", state2: "" })the issue here is using useEffect for user actions
both of your usecases are good (according to docs), but only on paper:
webhookSocketbut 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.
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 on activePage to an explicit page param. that way you can call it whenever you want with the parameters you needgetClips() in a run-once useEffect (we should supply getClips as a dependency for useEffect, and thus wrap it in useCallback to make it stable)clipsDetails, dont use a potentially stale value for clipsDetails 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 increment activePage.const Mycomponent = () => {
const [clipcount, setClipcount] = useState(0);
const [clipsDetails, setClipsDetails] = useState([]);
const [activePage, setActivePage] = useState(1);
const [socketClipCount, setSocketClipCount] = useState(0);
useEffect(() => {
// i'm 99% sure you dont need the webhookSocket instance inside the component scope. unless it dependes on some component state (like a room-id) you should move it outside of the component definition. that way its stable and not required as dependency.
webhookSocket.on(`livecut${id}`, (data) => {
setSocketClipCount((socketClipCount) => socketClipCount + 1);
});
return () => {
// unsubscribe from webhookSocket
};
}, []);
const getClips = useCallback(async (page) => {
const { value } = await getAllClips({
limit: clipLimit,
pageNo: page,
sort: { _id: -1 }
});
if (value?.success) {
setClipcount(value.totalLength);
setClipsDetails((prev) => [...prev, ...value?.clips]); // use the current value of clipsDetails in the spread
}
}, []);
useEffect(() => {
getClips();
}, [getClips]);
return (
<div className="custom-generated-clips styled-scroll">
{socketClipCount > 0 && ( // dont use non booleans with `&&` in jsx, as this will render the 0 as text
<button
onClick={() => {
getClips({ page: activePage });
setSocketClipCount(0);
}}
>
Refresh ({socketClipCount})
</button>
)}
<InfiniteScroll
dataLength={clipsDetails?.length}
hasMore={clipsDetails?.length < clipcount} // `? true : false` not needed as this already returns boolean
next={() => {
setActivePage(activePage + 1);
getClips({ page: 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;
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
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
useEffect(() => {
// avoid loading when refreshing or initializing
if(socketClipCount > 0) {
getClips();
}
}, [activePage, socketClipCount])
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
useEffect(() => {
webhookSocket.on(`livecut${id}`, (data) => {
setSocketClipCount((socketClipCount) => socketClipCount + 1);
});
}, [webhookSocket]);
May be closing the connection for few seconds when refreshing the open it again would be a good solution
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With