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
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.
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'm storing my form inputs in React component state. When I submit the form, I trigger a Redux action. And when this action succeeds, I want to update the state again - to clear the form. But how to do it?
I mean, I can easily store form state in Redux too and everything will be resolved, but I'd prefer to store component specific things in component state.
You should be using something like redux-thunk to delay the dispatching until the API call succeeds:
const postForm = data => dispatch => fetch(...).then((...) => dispatch(...))
Since fetch returns a Promise, you can then wait until it's resolved (api call succeeded) before performing the form clearing in your component:
props.postForm(...)
.then(() => this.setState(<clear the form state>))
.catch(<do something to warn the user api call failed?>)
What does that action update on the state exactly?
One way would be to add an extra case in your componentWillReceiveProps that handle that update of the form. If the action let say updates the list, you could have something like the following on your componentWillReceiveProps method inside you component:
componentWillReceiveProps(nextProps) {
if (nextProps.list !== this.props.list) {
this.setState({
formFields: this.getNewClearFormFields()
})
}
}
Where getNewClearFormFields is a function that returns your new form fields
If you want to update the state after redux action succeeds, then I would suggest go ahead and put it in componentWillReceiveProps by comparing prevState and nextState
use mapStateToProps() to map redux state to component
and then update the component state like below
componentWillReceiveProps(nextProps) {
this.setState({
...
});
}
My component has a check box that will fire off a this.props.toggleChange action creator when clicked. In the toggleChange action creator, it will dispatch a TOGGLE_CONFIG_CHANGE, which is picked up by a Redux Saga. The inside of the saga function looks like this:
export function* toggledConfigSaga () {
yield put(toggleConfigClicked()); // this fires off TOGGLE_CONFIG_CLICKED
yield put(saveAllCurrentConfigsAndFetchServerStuff()); // this function will throw up a spinner as asynch actions complete
}
In my reducer, I have CASE types.TOGGLE_CONFIG_CLICKED: return {...state, toggleConfig: !state.configChecked};. In redux dev tools and console log the configClicked boolean is changed instantaneously on check box click(as one would expect). However, the check mark doesn't toggle until the asynch actions completes, giving it the illusion of a laggy button. What gives?
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 }))
}