I want to cancel some functions after component unmount because it causes memory leak my code looks like this
componentDidUpdate(prevProps) {
if (prevProps.org.org !== this.props.org.org && this.mounted) {
this.props.clearGraph();
this.props.graphGet(this.props.org.org);
this.setState({ org: this.props.org.org });
}
}
componentDidMount() {
const abox = "a" + this.props.org.org.substr("1");
this.props.getHistory(abox);
this.props.graphGet(this.props.org.org);
}
componentWillUnmount() {
}
all I want is to cancel graphGet which is a redux action
You cannot cancel Redux actions by design. Once they get dispatched, they are processed by reducers and the state transition is performed.
You can however dispatch another action to revert the state changes or causing side effects. Normally you would use another library like redux-observable for the latter.
You can for example define 3 actions: START_GRAPH_GET, CANCEL_GRAPH_GET and FINISH_GRAPH_GET. On START you start your fetch, on CANCEL you cancel any outstanding fetches and once a fetch completes you dispatch FINISH and keep the result in the store.
In order to render the results you would need to use react-redux connect with a mapStateToProps function.
To cancel on unmount, you would just dispatch an CANCEL action, if necessary.
Since your code does not show anything related to Redux at all, I think a more general answer is reasonable here.
Related
As my app grows, now redux state bugs appear. This is because there will be cases where an async action has not been completely dispatched, but other action is dispatched and completed before previous action completed.
Is there a way to make an action waits for prev action to completely dispatched?
The dispatched actions function call will happen on different functions or even in different react components, so promise form of action would not be the solution. I need something that can manage the sequence of actions dispatched by redux, and when needed we can pass sth like isWaitToComplete parameter to the action, so that redux knows it will need to wait that particular action before dispatching another action.
My solution like yours, is to set a global status isWaitToComplete in your reducer,then change it to true or false,if the last action did't finish,then will not execute the next one.
Temporarily this is how I solved the issue:
let isLoading = false // alias of isWaitToComplete
export default function (state = initialState, action) {
switch (action.type) {
case types.SCREENS_UPDATE_STARTED: {
...
isLoading = true
...
}
case types.SCREENS_UPDATE_SUCCESS: {
...
isLoading = false
...
}
case types.SCREENS_SELECT: {
const runAfterPrevActionCompletelyDispatched = () => {
if (isLoading) {
// retry
setTimeout(runAfterPrevActionCompletelyDispatched, 100)
}
// state update logic here ...
return state
.set('screens', fromJS(screens))
.set('selectedScreen', fromJS(selectedScreen))
.set('selectedScreenIds', fromJS(selectedScreenIds))
}
return runAfterPrevActionCompletelyDispatched()
}
}
}
Actually I hope to have a middleware or built-in functionality from redux itself to address this issue. As this is basic task for state management library that could happen in many cases. If you have other cleaner solution please post here also.
I faced the same issue, the solution I found rely on a change in the pattern.
According to the redux style guide:
It is strongly recommended to dispatch actions that are processed by
several reducers.
It is strongly recommended to put the logic in the reducers.
Following those rules, you should avoid chaining dispatch by dispatching an action with all the data needed in the payload and then processed it in the reducer. This way, you can stop being dependent of a previous update.
Hope I am clear, hope it help
I have React application which manages state using React context. I've created simple counter incrementation reproduction.
There are two contexts. One for storing state, and the second one for dispatching. It's a pattern taken from this article.
The state context just stores single number and there is only one action that can be invoked on this state:
function reducer(state: State, action: Action): State {
switch (action.type) {
case "inc": {
return state + 1;
}
}
}
I also have two helper functions for incrementing value synchronously and asynchronously:
async function asyncInc(dispatch: React.Dispatch<Action>) {
await delay(1000);
dispatch({ type: "inc" });
dispatch({ type: "inc" });
}
function syncInc(dispatch: React.Dispatch<Action>) {
dispatch({ type: "inc" });
dispatch({ type: "inc" });
}
And here is how you use it in the component:
const counter = useCounterState();
const dispatch = useCounterDispatch();
return (
<React.Fragment>
<button onClick={() => asyncInc(dispatch)}>async increment</button>
<button onClick={() => syncInc(dispatch)}>sync increment</button>
<div>{counter}</div>
</React.Fragment>
);
Now, when I click the sync increment button everything will work as expected. It will invoke the inc operation twice, incrementing counter by 2 and perform only one rerender of the component.
When I click the async increment button it will first wait for one second and perform inc operation twice but it will rerender component twice.
You have to open console to see logs from rendering components.
I kinda understand why is that. When it's a synchronous operation, everything happens during component rendering, so it will first manipulate state and render at the end. When it's an asynchronous operation, it will first render component and after one second it will update state once triggering rerender and it will update for the second time triggering next rerender.
So is it possible to perform asynchronous state update doing only one rerender? In the same reproduction, there is similar example but using React.useState that is also having the same problem.
Can, we somehow batch updates? Or I have to create another action that would perform several operations on the state at once?
I've also created this reproduction that solves this problem by taking array of actions but I'm curious if it can be avoided.
Basically what you're seeing for syncInc() is batching for click events. Thus, you only see it render once.
React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
For your asyncInc(), it is outside the scope of the event handler (due to async) so it is expected you get two re-renders (i.e doesn't batch state updates).
Can, we somehow batch updates?
Yes React can batch updates within an async function.
Unless this is causing a performance problem I would recommend not worrying about additional renders. I found this post about fixing slow renders before worrying about re-renders to be helpful on this topic.
However, it does look like React has an unstable (so it shouldn't be used) API for batching updates ReactDOM.unstable_batchedUpdates. However, using this could cause a headache in the future if it's removed or changed. But to answer your question "can we somehow batch updates?", yes.
const asyncInc = async () => {
await delay(1000);
ReactDOM.unstable_batchedUpdates(() => {
setCounter(counter + 1);
setCounter(counter + 1);
});
};
I want to cancel some functions after component unmount because it causes memory leak my code looks like this
componentDidUpdate(prevProps) {
if (prevProps.org.org !== this.props.org.org && this.mounted) {
this.props.clearGraph();
this.props.graphGet(this.props.org.org);
this.setState({ org: this.props.org.org });
}
}
componentDidMount() {
const abox = "a" + this.props.org.org.substr("1");
this.props.getHistory(abox);
this.props.graphGet(this.props.org.org);
}
componentWillUnmount() {
}
all I want is to cancel graphGet which is a redux action
You cannot cancel Redux actions by design. Once they get dispatched, they are processed by reducers and the state transition is performed.
You can however dispatch another action to revert the state changes or causing side effects. Normally you would use another library like redux-observable for the latter.
You can for example define 3 actions: START_GRAPH_GET, CANCEL_GRAPH_GET and FINISH_GRAPH_GET. On START you start your fetch, on CANCEL you cancel any outstanding fetches and once a fetch completes you dispatch FINISH and keep the result in the store.
In order to render the results you would need to use react-redux connect with a mapStateToProps function.
To cancel on unmount, you would just dispatch an CANCEL action, if necessary.
Since your code does not show anything related to Redux at all, I think a more general answer is reasonable here.
Or in other words, how do i know when a action that's dispatched is complete?
I have a webapp where I'd update the redux store through dispatches on user input. If i push the redux store's state to the server right after dispatching, the server does not have the most updated info. I need to wait for the action to complete prior to pushing to server, hence the question.
EDIT
Based on markerikson's answer I solved it. I thought dispatch was called asynchronously but the execution of the group of dispatched actions happened synchronously. That's wrong and mark's right. My issue was the the update server call was made in a component and it pushes to server a prop that needed to be updated by redux-store. That updating of the prop didn't happen fast enough.
A little late to the question, but for the people who got here based on the question title - take a look into:
store.subscribe()
store.getState()
where 'store' is a reference to your redux store.
store.subscribe(() => {
const newState = store.getState();
// check out your updated state
});
the code above would run anytime there is an update to the redux state
Dispatching is entirely synchronous, unless altered by a middleware. So, as soon as the call to dispatch() returns, the store state has been updated and all subscribers have been notified.
If you need to wait for some async behavior to complete, you can write a thunk that returns a promise, and chain off that when you dispatch:
function someThunkReturningAPromise() {
return (dispatch) => {
const promise = myAjaxLib.fetchData().then(response => {
dispatch(loadData(response.data));
});
return promise;
}
}
dispatch(someThunkReturningAPromise()).then( () => { });
I want to set form loading state (spinner icon, disable input) when the user submits the form, and clear that state when the action completes or fails. Currently I am storing this state in my store, which involves creating an action creator and reducer, which is annoying for a few reasons:
It's a lot more work than simply calling this.setState
It's more difficult to reason about
I don't really want to store local component state in my store
Essentially I want to do this within my form component:
handleFormSubmitted() {
this.setState({ isSaving: true })
this.props.dispatchSaveForm({ formData: this.props.formData })
.finally(() => this.setState({ isSaving: false }))
}
You can't do that with redux-saga. What you are trying to do there goes against the basic principles of redux-saga.
redux-saga aims to be reactive by treating actions like events that describe what's happening in your app... So that other sagas (usually "watchers" using take) or the rootReducer can subscribe to those actions/events and do what they need to do...
Because of that redux-saga -unlike redux-thunk and redux-promise-
doesn't change the behaviour of the dispatch method... So, with redux saga when you dispatch, you dispatch, and the reducers and the sagas are subscribed to the dispatched actions. But the dispatch method won't return a promise like it happens when you use other middlewares/store-enhancers.
So, the only way that redux-saga has to let the rest of the app know that the request of your form has finished is by dispatching an action (using the put effect) whenever that request finishes or errors, right? So, how could you possibly know directly from inside the component if a specific action has been dispatched?
Unless you make your own middleware (or you use a separate one) with a connector component: there is no way for you to subscribe to concrete actions inside a component.
Sure, you could access the context directly in order to get a hold of your redux store, and then you could use the redux subscribe method directly, but the listener function won't tell you what's the action that got dispatched. It will just get invoked when an action (any action) gets dispatched... maybe you could check if some property of the state has changed, but that's insane. So, unless you want to go down that route, which is crazy: you can't do that using redux-saga.
If you wanted to do something like that (which IMHO is not a very good idea) you would have to do it without using redux-saga. A possible way to do it could be something along the lines of:
handleFormSubmitted() {
this.setState({ isSaving: true })
yourFetchCall({ formData: this.props.formData })
.then(payload => this.props.dispatchFormSaved(payload))
.catch(error => this.props.dispatchSavingFormErrored(error))
.finally(() => this.setState({ isSaving: false }))
}