Redux: Dispatching an action that is not affecting the state - reactjs

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..');
});
}
}

Related

reducer return state twice, how to update in store?

My reducer returns state in a async way, since it will interact with another component, how to make the component re-render after reducer update state in the .then?
switch (action.type) {
case RELEASE_PAYMENT:
(new PaypalContract())
.releasePayment(action.id)
.then(() => (new PaypalContract()).getState(action.id))
.then(function(e) {
console.log("status in orderReducer:",status[e])
return{
...state,
orderStatus: status[e]
}
})
return state
You can't return the state twice from the reducer function and you can't return the updated state from the reducer function in an asynchronous way.
Returning something from the callback function of the .then() method doesn't makes it a return value of the outer function, i.e. reducer function in your case.
Reducer functions are synchronous, they take in a action and based on the action return the updated state. All of this is done synchronously.
What you are doing in your reducer function won't work as you expect it to and the return statement at the of your reducer function will always execute before asynchronous code completes. This means that your reducer function always returns the same state which means that your redux store never updates.
Solution
What you need is a way to dispatch an action that triggers the data fetching and once the data is available, another action should be dispatched that is received by the reducer function. This new action should contain the data that is fetched asynchronously, as a payload and the reducer function will just update the state appropriately using the payload associated with the action.
Following are the couple of options that allow you to do just that:
redux thunk
Redux Saga
Using the first option is easier so i would recommend to start with that but you could also explore the second option which also help with advanced use cases.
Here are couple of resources to get you started with each of the options mentioned above:
Redux Thunk Explained with Examples
Redux Saga Example
You need to pass the data to the reducer after you get the response from the API. It will be simpler to control the flow of data. You can make use of async/await in this.
async function_name() {
const res = await API_CALL_HERE
res.then( data => INVOKE_YOUR_DISPATCH_HERE(data))
.catch(err => console.log('error'));
}
In reducer
case RELEASE_PAYMENT:
return {...state, orderStauts:data[e]

Redux: request into success or error flow from Component (using redux-saga)

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.

redux-sagas callback (aka sagas and setState)

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 }))
}

When and where should components get their data from the server?

(This is maybe not a redux question per sé)
My current strategy/thoughtprocess is this: every container component that needs data, dispatches an action that fetches that data in componentDidMount (which then triggers an api request, that dispatches an action that updates the store)
The problem with this: some components are fetching the exact same data. This also means that when I load my app a (sometimes "large") number of unneccessary requests and rerenders happen.
My question: what's a better way of handling this?
Personally I see two ways:
somehow cache the data returned from the server, and if a new request is triggered that has cached data (and happens within a certain timeout), return that cached data instead of making a new request.
fetch all my app data somewhere else in one spot. (the drawback I see is that my container components aren't "self-sufficient" then)
My guess is that there is some better way that I can't seem to see :)
To improve the initial render, I would:
fetch the data on the server and populate redux store
do server rendering and pass the redux store state to client
add a check to your componentDidMount to see if the data is available
Something like
componentDidMount() {
if (this.props.shouldFetchData) {
this.props.fetchData()
}
}
We have a system like this in place and it works fine. The only big downside is that you have to do the api call in 2 different places ( client and server ), but I've yet to see a solution with server side rendering that doesn't have that duplication.
I like to use redux-thunk for this type of thing as you can get access to the state and make decisions accordingly. e.g.
const fetchData = (id) => {
return (dispatch, getState) => {
if (getState().path.to.data.isLoading)
return
dispatch({type: 'LOADING_DATA'})
fetch(`${url}/${id}`).then(data => dispatch({type: 'SET_DATA', data}))
}
}
Then in your reducer, you set isLoading true when the 'LOADING_DATA' action is dispatched and then toggle it back to false when the 'SET_DATA' is received. e.g.
const initialState = {isLoading: false}
const reducer = (state = initialState, action) => {
switch(action.type) {
case 'LOADING_DATA':
return { ...state, isLoading: true }
case 'SET_DATA':
return { ...state, ...data, isLoading: false }
}
}
All components looking for the same data in the store will be rerendered when the store is updated.
You can also extend this pattern as much as you like to suit your need, for example, store a timestamp instead of a boolean and don't follow through on the request for a certain timeout.

Pass values as parameters state from the component or access the status in the action creator?

In my project I have action creator that depend on values ​​that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values ​​within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).

Resources