I have a page with multiple forms on it. Anytime the form is submitted, I increase the value of actionsInARow (that is stored in context). What I want to do is, as soon as this value is >0, start a 2 second timer where we will re-fetch data from the database. But, anytime the user submits another form, I want to reset the timer. Below is the implementation I have so far, implemented on the component at the page level. Is there a better way to do this?
const [timerId, setTimerId] = React.useState(0);
useEffect(() => {
if(actionsInARow === 0) {
return;
}
if(timerId !== 0) {
window.clearTimeout(timerId)
}
let timeoutId
timeoutId = window.setTimeout(() => {
setTimerId(0)
// dispach event in context to reset the count to 0
dispatch({ type: "RESET_COUNT" });
// re-run fetch request here...
}, 2000)
setTimerId(timeoutId);
return () => {
if(timeoutId !== 0) {
window.clearTimeout(timeoutId)
}
}
}, [actionsInARow]);
Well for one thing you don't need the duplicate code to clear the timeout, the cleanup function will handle that for you so remove those 3 lines in the body. You also don't need the state of timerId, unless you're using that somewhere else.
Those changes would look like this
useEffect(() => {
if(actionsInARow === 0) {
return;
}
let timeoutId = window.setTimeout(() => {
// dispach event in context to reset the count to 0
dispatch({ type: "RESET_COUNT" });
// re-run fetch request here...
}, 2000)
return () => {
window.clearTimeout(timeoutId)
}
}, [actionsInARow]);
Related
//this is an function that receive real time msg that other user send.
api call to get conversation list...
const handleListMyContact = useCallback(async () => {
const data = await getConversation(authToken);
if (data) {
setListData(data.info);
} else {
console.log("loading data...");
}
}, []);
useEffect(() => {
if (focus) {
handleListMyContact();
}
}, [handleListMyContact, focus]);
const receivePersonalMessage = useCallback((data: any) => {
console.log("data from emit", data);
const contactList: any = listData;
const idxObj = contactList.findIndex((object: any) => {
return object.conversation_id === data.conversation_id;
});
console.log("object find....", idxObj);
contactList.splice(idxObj, 1);
contactList.unshift(data);
console.log("contact list", contactList[0].message);
setListData((prev: any) => [contactList[0], ...prev]);
console.log("clicked...");
handleListMyContact(); // this is my api call that code is above
}, []);
//then I set socket globally. so in useEffect i direct call that socket.on instance.
useEffect(() => {
socket.on("new_conversation_message", receivePersonalMessage);
return () => {
socket.off("new_conversation_message", receivePersonalMessage);
};
}, [socket, receivePersonalMessage]);
whenever other user send the msg at the conversation tab i can see first two response then it render with original one.
// this is log i got from receivePersonalMessage function
object find.... -1
contact list Hi
clicked...
object find.... -1
contact list Hi
clicked...
and the problem is first it shows me hi msg first time in alone then marge with group but actually I want hi msg should be direct update to the group not two times in another user screen then show in orginal group. could you please help me out? I really appreciate if you could help me. Thank you in advance
I have a useEffect set up how I thought would only run once on initial render but it continues to rerun.
This breaks a function that is supposed to set a piece of state to true if a condition is truthy and show appropriate UI.
This sort of works but then the useEffect runs again flicks back to false immediately. I am also using a use effect to check on first render if the condition is truthy and show appropriate UI if so.
Basically when setIsPatched is true I don't want the useEffect to rerun because it flicks it back to false and breaks the UI
Here is the function:
const [isPatched, setIsPatched] = useState<boolean>(false);
useEffect(() => {
getApplied(x);
}, []);
const getApplied = (x: any) => {
console.log(x);
if (x.Log) {
setIsPatched(true);
return;
} else {
setIsPatched(false);
}
};
I also pass getApplied() to child component which passes a updated data to the function for use in this parent component:
const updatePatch = async (id: string) => {
//check if patch already in db
const content = await services.data.getContent(id);
const infoToUpdate = content?.data[0] as CN;
if (!infoToUpdate.applyLog && infoToUpdate.type == "1") {
// add applyLog property to indicate it is patched and put in DB
infoToUpdate.applyLog = [
{ clID: clID ?? "", usID: usID, appliedAt: Date.now() },
];
if (content) {
await services.data
.updateX(id, content, usId)
.then(() => {
if (mainRef.current) {
setDisabled(true);
}
});
}
getApplied(infoToUpdate);
} else {
console.log("retrying");
setTimeout(() => updatePatch(id), 1000); // retries after 1 second delay
}
};
updatePatch(id);
}
I'm trying to do something that in my mind is very simple.
I have an array of documents (firebase firestore) and I have a method to fetch From Document with timeStamp A to docuemnt with timeStamp B
In the fetch function that tries to see if the ref id has already been fetched, but the messages inside the fetchUpTo function never updates. While the one I log in the effect hook, updates as expected.
The top Log is the Inside fetchUpTo and the bottom one is the effect one.
The logs are from trying to refetch one of the documents present in the bottom log.
const fetchUpTo = (ref: any) => {
if (isFetching || isAtEnd) {
return
};
if (!messagesSnapShot) {
return;
}
if (messagesSnapShot.size < queryLimit) {
return;
}
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
setIsFetching(true)
const lastVisible = messages[0]
const cQuery = query(collection(fireStore, "scab-chat", chatId, "messages"), orderBy('time', 'desc'), startAfter(lastVisible.data.time), endAt(ref.data.time));
getDocs(cQuery).then(newDocs => {
if (newDocs.size < queryLimit) {
setIsAtEnd(true)
}
const newD = newDocs.docs.map(doc => ({
data: doc.data(),
id: doc.id,
ref: doc
}));
setMessages(s => {
const f = newD.filter(doc => s.findIndex(d => d.id === doc.id) === -1)
return [...s, ...f]
})
})
}
After doing this, I "wait" for the state to update with an Effect hook
useEffect(() => {
if (messages) {
setIsFetching(false)
}
}, [messages])
The problem is I have this small part of the code
let index = messages.findIndex(d => d.id === ref.id)
if (index !== -1) {
if (messageRefs.current[index] !== null) {
scrollToMessage(messageRefs.current[index])
return;
}
}
React state will only rerender you app when the function finishes, so you will only check for the updated messages when you call fetchUpTo again. If you need the updated value on the same function call, try using flushSync.
There is a nice video with Dan Abramov trying to achieve the same as you, I will leave it here for reference: https://youtu.be/uqII0AOW1NM?t=2102
Okay so I fixed it kinda, I had to render a diferent component while the isFetchingState was true so
if(isFetching){return <Loader/>}
and then it worked. I still don't really understand why It didn't work in the first place.
I am having a problem that is when I change user's open time, the app works well in the first change, but from the second change, it will clear the array that store time data and then the process will restart from the beginning. If I don't put anything in useEffect dependency, the app works correctly, but I have to reload the app every time the date is changed.
Here is the code to deal with the open time data:
curStreakUpdate = (date: string) => {
const nearliestDateOpen = this.currentStreak[this.currentStreak.length - 1];
const dateNear: any = new Date(nearliestDateOpen);
const dateNow: any = new Date(date);
if (this.currentStreak.length === 0) {
this.currentStreak.push(date);
this.setLongestStreak(this.currentStreak.length);
this.totalPrays += 1;
} else {
this.checkNearliestOpenDate(dateNow, dateNear, date);
}
console.log(this.currentStreak);
};
checkNearliestOpenDate = (dateNow: any, dateNear: any, date: string) => {
if (dateNow - dateNear !== 0) {
if (dateNow - dateNear === 86400000) {
if (!this.currentStreak.includes(date)) {
this.currentStreak.push(date);
this.setLongestStreak(this.currentStreak.length);
}
} else {
this.currentStreak = [];
}
this.totalPrays += 1;
}
};
-Here is where I use useEffect hook to store open time whenever user open the app:
const {curStreakUpdate} = useStore().streakStore;
const dt = new Date('2022-09-14').toISOString().split('T')[0];
useEffect(() => {
AppState.addEventListener('change', (state) => {
if (state === 'active') {
curStreakUpdate(dt);
// AsyncStorage.clear();
}
});
}, [dt]);
Here is the output
Array [
"2022-09-15",
]
Array [
"2022-09-15",
]
Array [
"2022-09-15",
"2022-09-16",
]
Array []
Array [
"2022-09-16",
]
If I don't put anything in useEffect dependency, the app works correctly, but I have to reload the app every time the date is changed.
You don't have to reload the app with cmd-r, I should be enough when you close/terminate the app.
The operating system iOS/Android usually kill an app which is in background for longer time.
So there is no need to kill the app manually in the real world for most cases.
checkNearliestOpenDate = (dateNow: any, dateNear: any, date: string) => {
if (dateNow - dateNear !== 0) {
if (dateNow - dateNear === 86400000) { // I think this should be >= or <=
if (!this.currentStreak.includes(date)) {
this.currentStreak.push(date);
this.setLongestStreak(this.currentStreak.length);
}
} else {
this.currentStreak = [];
}
this.totalPrays += 1;
}
};
Additionally you should cleanup your listener otherwise you would add another listener the view get opened.
And you have to ensure you haven't already added a listener currently you would add a listener each time dt changed.
I don't care if you really need the useEffect at all for what you wanna implement.
useEffect(() => {
const listener = AppState.addEventListener('change', (state) => {
if (state === 'active') {
curStreakUpdate(dt);
// AsyncStorage.clear();
})
return () =>
listener.remove()
});
}, [dt]);
please help with useCallback, how to call it so that useEffect does not send endless requests to the server? Now it sends endless requests, I know that useCallback is needed for this, but I don’t understand how to use it
// Get data
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
}, [api, leagues, sidebars, countries, onTest]);
// The 'onTest' function makes the dependencies of useEffect Hook (at line 29) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'onTest' definition into its own useCallback() Hook
// test
const onTest = () => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
};
Req:
I'm assuming you want to load data once, normally done with componentDidMount.
2 useEffect fired only once, running in parralel
useEffect(() => {
api.getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
api.getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
useEffect running only on data changes
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
}
}, [leagues, countries]);
Update
api defined earlier ...
const api = new Services();
of course changes on every render but should not be taken as dependency as it's always uses the same urls - they are not parametrized. No meaningful (affecting data) changes - don't care.
If api is not passed to rendered components (ref cange could force rerenderings) then there is no need to take care about it.
You can get rid of this definition by
useEffect(() => {
new Service().getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
new Service().getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
Even if it looks as unnecessary duplicating instances it can be more optimal when component is frequently rerendered - like in open-close view changes.
You're in an infinite loop because your effect depends on values you change in it: each time you fetch some data from the api you change leagues and countries states which leads to running the effect again.
Split the effects so that the requests effect does not depend on the values it changes:
useEffect(() => {
api.getTestLeagues().then(data => setLeagues(data));
api.getTestCountries().then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest();
}
}, [leagues, sidebars, countries, onTest]);
Also:
Define api outside of your component (above) so the value stays stable.
You can wrap onTest in a useCallback before your effects, like #Ramesh suggested.
Try wrapping onTest with useCallback like this:
const onTest = useCallback(() => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
},[]);
The second argument to useCallback is an array of dependencies similar to useEffect.
BTW this will memorize the function so that a new function doesn't get created on every render.
update
It is better to split the useEffect so that each useEffect just focuses on executing something when their dependencies are changed.
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
},[onTest, sidebars, leagues, countries])
When you send a request that changes the state and also use the same state as a dependency that'll again send a request(because the value of the state changes) it'll cause an infinite loop.
update 2
The issue might be with the api class instance because on every new render a new instance is created.
You said you were doing something like this const api = new Services(); so wrap that with useMemo like this:
const api = useMemo(() => {
return new Services();
})