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 }))
}
Related
Need to call multiple dispatches. Trying to use batch from react-redux, but it triggers only the first dispatch inside the function. Tried to exchange dispatches, the same, triggers only the first one, checked in dev tools. Thunk is connected
In app:
dispatch(
setData({
user,
userLang,
userToken,
dateMargin,
}), )
Action:
export const setData = ({user, userLang, userToken, dateMargin}) => {
return dispatch => {
batch(() => {
dispatch(setToken(userToken))
dispatch(setLang(userLang))
dispatch(setUser(user))
dispatch(setDateMargin(dateMargin))
})
}
}
Yes, batch is a re-export from react-dom. React-native does not have a similar API, so that is not possible there.
Generally, it is recommended to Model Actions as Events, Not Setters, Allow Many Reducers to Respond to the Same Action and to Avoid Dispatching Many Actions Sequentially (see the links for more explanation), so what you are doing here is pretty much an anti-pattern:
You have the logic in your component, although the big strentgh of Redux is that it moves the state logic out of your component, into your Store.
Going by the recommendations from the official Redux Style guide, you'd rather
dispatch(userLoggedIn({
userToken,
userLang,
user,
dateMargin
})
and have potentially multiple reducers acting on that event-type action.
We handle side-effects in middlewares, not in reducers, which means we always need to dispatch an action to handle side-effects. Do you think it's proper to dispatch an action, which is not changing the state, to make an API call? I've always avoided it because Action stands for changing the State in my understanding.
I am also handling it in my projects just like you described (only dispatching Actions, which are changing the state, but also having an side effect for api calls).
Whats wrong with doing so, but you have the additional attributes loading and loaded in your state?
So within your reducer function for the initial Action, you would only set loading = true and trigger the side effect.
Within the reducer function for the Success Action you would set your state like this:
return {
...state,
data: action.payload,
loading: false,
loaded: true
};
This also has the advantage that you can easily display a loading indicator, if your rest call takes a while.
You can use action generators which may or may not dispatch actions as per your need
This is a action generator which is used to remove a library item from store on a condition that if it is found on the server otherwise not
export const startRemoveLibraryItem = (libraryItemId) => {
return (dispatch) => {
return database.ref(`library/libraryItems/${libraryItemId}`)
.then((snap) => {
// if item found on server I am going to dispatch a action now
if(snap.val() == something)
dispatch(removeLibraryItem(libraryItemId));
})
.catch((e) => {
// item some error do not do anything
console.log('Error in removing Library Item..');
});
}
}
I have a state that specify which component render(component A or B).
This state is determined when my action dispatch specific type(for example GO_TO_B or GO_TO_A).
Then I need to fetch some config from server to render component A. I want these config be in store.So I should call a action(for example fetchConfig() ) to async fetch data from server and dispatch response.
My question is that where i call the fetchConfig() action.
if I call this action in componentDidMount() in component A the error occur that cannot dispatch middle of dispatch.
So which method in react life cycle call after dispatch process and before render ?
I understand you are using redux.
If that's correct, i recommend to do your fetching with a thunk.
redux-thunk is a middleware that allows you to dispatch functions (instead of serialized objetcs like actions), that way you can deley a dispatch of an action or even dispatch it conditionaly.
An example of a thunk would be like that:
function loadSomeThings() {
return dispatch => {
fetchFirstThingAsync.then(data => { // first API call
dispatch({ type: 'FIRST_THING_SUCESS', data }); // you can dispatch this action if you want to let reducers take care of the first API call
return fetchSecondThingAsync(data), // another API call with the data received from the first call that returns a promise
})
.then(data => {
dispatch({ type: 'SECOND_THING_SUCESS', data }); // the reducers will handle this one as its the object they are waiting for
});
};
}
You can notice we can even chain ajax requests and we can dispatch (if we want) an action on each success (OR FAIL!).
I recommend reading the docs to understand it better.
You can use componentWillMount : Doc.
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.
This is the one thing that I haven't found a standard solution to yet.
I have my store setup with redux-saga for side effect handling, I dispatch an action (that has async side effects) from a component, and now want the component to do something once the side effects are handled (for example navigate to another route/screen, popup a modal/toast or anything else).
Also I want to display a loading indicator or any errors on failure.
Before redux, this kind of flow was straight forward, it'd look something like this:
try {
this.setState({loading: true});
const result = await doSomeRequest();
this.setState({item: result, loading: false});
} catch (e) {
this.setState({loading: false, error: e});
}
With redux, I'd typically want to dispatch an action initiating the flow and have all related information in the store, to allow many components to listen to what is happening.
I could have 3 actions, 'requested', 'success', 'failed'.
In my component I would dispatch the requested action.
The responsible saga will then dispatch either the 'success' or 'failed' action upon handling 'requested'.
My Component will reflect on the changes.
But I haven't found out a standard way to figure out if the action has completed. Maybe the store hasn't updated as result of the async action (NO-OP, but loading state would still change I guess). But the action still succeeded, and I want to do something like navigate to another screen.
I really tried finding this kind of (seemingly common) scenario in the redux docs, redux-saga docs or Stackoverflow/Google, but no success.
Note: also with redux-thunk I think this behaviour is straight forward to achieve, since I can just .then on an async action dispatch and would receive the success action or the error action in catch (correct me if I'm wrong, never really used thunk). But I haven't seen the same behaviour achieved with redux-saga yet.
I've come up with 3 concrete solutions:
Most primitive solution, handling only the 'success'/'failed' actions from the component. Now this solution I am not a big fan of. In my concrete implementation there is no action that indicates that the async request has been started. The side effects are handled right there in the Component, instead of being abstracted away within a saga. Lots of potential code repitition.
Running a one time saga right before dispatching the request action, that races the 'success'/'failed' actions against each other and allows to react on the first occurring action. For this I've written a helper that abstracts the running of the saga away: https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js
This example I like a lot more than 1. since it's simple and declarative. Though I don't know if creating a saga during run time like this has any negative consequences, or maybe there is another 'proper' way to achieve what I'm doing with redux-saga?
Putting everything related to the action (loading, successFlag, error) into the store, and reacting in componentWillReceiveProps on action changes ((!this.props.success && nextProps.success)) means the action has completed successful).
This is similar to the second example, but works with whatever side effect handling solution you choose.
Maybe I'm overseeing something like the detection of an action succeeding not working if props hail in very fast and props going into componentWillReceiveProps will 'pile up' and the component skips the transition from non-success to success altogether?
Please feel free to have a look at the example project I've created for this question, that has the full example solutions implemented: https://github.com/milanju/redux-post-handling-example
I would love some input on the methods I use to handle the described flow of actions.
Am I misunderstanding something here? The 'solutions' I came up with were not straight forward to me at all. Maybe I'm looking at the problem from the wrong angle.
Are there any issues with the examples above?
Are there any best practice or standard solutions for this problem?
How do you handle the described flow?
Thanks for reading.
If I understand your question correctly, you want your component to take action based on actions fired off by your saga. This would typically happen in componentWillReceiveProps - that method is called with the new props while the old props are still available via this.props.
You then compare the state (requested / succeeded / failed) to the old state, and handle the various transitions accordingly.
Let me know if I've misinterpreted something.
I achieved the point of having an asynchronous action callback in a component using saga the following way:
class CallbackableComponent extends Component {
constructor() {
super()
this.state = {
asyncActionId: null,
}
}
onTriggerAction = event => {
if (this.state.asyncActionId) return; // Only once at a time
const asyncActionId = randomHash();
this.setState({
asyncActionId
})
this.props.asyncActionWithId({
actionId: asyncActionId,
...whateverParams
})
}
static getDerivedStateFromProps(newProps, prevState) {
if (prevState.asyncActionId) {
const returnedQuery = newProps.queries.find(q => q.id === prevState.asyncActionId)
return {
asyncActionId: get(returnedQuery, 'status', '') === 'PENDING' ? returnedQuery.id : null
}
}
return null;
}
}
With the queries reducer like this:
import get from 'lodash.get'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const FAIL = 'FAIL'
export default (state = [], action) => {
const id = get(action, 'config.actionId')
if (/REQUEST_DATA_(POST|PUT|DELETE|PATCH)_(.*)/.test(action.type)) {
return state.concat({
id,
status: PENDING,
})
} else if (
/(SUCCESS|FAIL)_DATA_(GET|POST|PUT|PATCH)_(.*)/.test(action.type)
) {
return state
.filter(s => s.status !== PENDING) // Delete the finished ones (SUCCESS/FAIL) in the next cycle
.map(
s =>
s.id === id
? {
id,
status: action.type.indexOf(SUCCESS) === 0 ? SUCCESS : FAIL,
}
: s
)
}
return state
}
In the end, my CallbackableComponent knows if the query has finished by checking if this.state.asyncActionId is present.
But this comes at the cost of:
Adding an entry to the store (though this is inevitable)
Adding a lot of complexity on the component.
I would expect:
the asyncActionId logic to be held on the saga side (eg. when the async action is connected using mapActionsToProps, it returns the id when called: const asyncActionId = this.props.asyncAction(params), like setTimeout)
the store part to be abstracted by redux-saga, just like react-router is in charge of adding the current route to the store.
For now, I can't see a cleaner way to achieve this. But I would love to get some insights on this!
Maybe I didn't understand the problem your facing but I guess they meant the client would look something like this:
mapStateToProps = state => {
return {
loading: state.loading,
error : state.error,
data : state.data
};
}
and the component will render like:
return(
{this.props.loading && <span>loading</span>}
{!this.props.loading && <span>{this.props.data}</span>}
{this.props.error && <span>error!</span>}
)
and when requested action is dispatched the its reducer will update the store state to be {loading: true, error: null}.
and when succeeded action is dispatched the its reducer will update the store state to be {loading: false, error: null, data: results}.
and when failed action is dispatched the its reducer will update the store state to be {loading: false, error: theError}.
this way you won't have to use componentWillReceiveProps .
I hope it was clear.
I don't really know why I can't get this to work. All the evidence talks against it...This is the situation:
I have a grid of data and a search panel. When the search panel is changed the searchparams are updated and used for updating the data grid.
The thing which triggers the chain is when the user changes the search panel. In my component i handle search panel changes with this:
getPhotos(key, value) {
const change = [{ key: key, value: value},{ key: 'page', value: 1}]
this.props.dispatch(updateSearchParams(change))
console.log('payload.searchParams', this.props.searchParams);
this.props.dispatch(
getPhotos(
{ context:this.props.params.context,
searchParams: this.props.searchParams }
)
);
}
Thus two dispatch calls to action creators form the component. The problem is that the searchparams are not updated in time for the getPhotos call, so the grid is not updated accordingly.
I thought that dispatch calls were synchronous - thus one after the other. I guess that it is the round trip from the component, to the action creator, to the store and reducer which is "screwing" it up.
The first call does not involve any asynchronous calls.
What is the "right" way of doing this? Please be specific about what goes in the component, the action creator and the reducer.
Thanks
dispatch is synchronous (unless you are using some middleware like redux-thunk). But after this.props.dispatch(updateSearchParams(change))
, your component needs to be updated (a re-render) or the this.props.searchParams is still the old one.
You can write this.props.dispatch(getPhotos(...)) in componentWillReceiveProps(nextProps), so you can access the new props (nextProps)
If you are using redux-thunk and two actions updateSearchParams and getPhotos are always bind together, you can create another aggregated action creator for them.
const updateSearchParams = change => dispatch => {
// return a promise here
// or use callback style etc. whatever you prefered
}
const updateSearchParamsAndGetPhotos = (change, context) => dispatch => {
dispatch(updateSearchParams(change))
.then(res => {
dispatch(getPhotos({
context,
searchParams: res.data.searchParams
}))
})
}
So now after dispatching a single action, your component should receive the new photos.
I had it wrong from the beginning.
The searchparams should not go into the store. I can handle the in the component alone - in the state of the component.
This the simplifies and eliminates the problem I described above.
Of cause there could be a situation where the searchparams needed to be available for other components. In that case I would go for #CodinCat answer above with the thunk. It works, i managed to implement it before my realisation.
Thanks