I am using react app context to store an array of "alerts objects" which is basically any errors that might occur and I would want to show in the top right corner of the website. The issue I am having is that the context is not being up to date inside a timeout. What I have done for testing is gotten a button to add an alert object to the context when clicked and another component maps through that array in the context and renders them. I want them to disappear after 5 seconds so I have added a timeout which filters the item that got just added and removes it. The issue is that inside the timeout the context.alerts array seems to have the same value as 5 seconds ago instead of using the latest value leading to issues and elements not being filtered out. I am not sure if there's something wrong with my logic here or am I using the context for the wrong thing?
onClick={() => {
const errorPopup = getPopup(); // Get's the alert object I need
context.setAlerts([errorPopup, ...context.alerts]);
setTimeout(() => {
context.setAlerts([
...context.alerts.filter(
(element) => element.id !== errorPopup.id,
),
]);
}, 5000);
}}
CodePudding user response:
onClick={() => {
const errorPopup = getPopup(); // Get's the alert object I need
context.setAlerts([errorPopup, ...context.alerts]);
setTimeout(() => {
context.setAlerts(alerts => [
...alerts.filter(
(element) => element.id !== errorPopup.id,
),
]);
}, 5000);
}}
This should fix it. Until react@17 the setStates
in an event handler are batched ( in react@18 all setStates
are batched even the async ones ), hence you need to use the most fresh state to make the update in second setAlerts
.
To be safe it's a good practice using the cb
syntax in the first setState as well.
CodePudding user response:
I think the fix would be to move context.setAlerts(...) to a separate function (say removePopupFromContext(id:string)) and then call this function inside the setTimeout by passing the errorPopup.Id as parameter.
I'm not sure of your implementation of context.setAlerts, but if it's based on just setState function, then alternatively, you could do also something similar to how React let's you access prevState in setState using a function which will let you skip the creation of the extra function which may lightly translate to:
setContext(prevContextState =>({
...prevContextState,
alerts: prevContextState.alerts.filter(your condition)
)})