Two simultaneous api calls, redux state properties becomes empty - reactjs

I call two action creators from a view component, which triggers two api calls from redux-sagas.
Both reducers have the same Action type - I don't know if that's good or bad, but it means that both reducers are triggered even if the data is handled by the other reducer.
Here is the first reducer:
case ADD_THINGS:
return {
...state,
termNodes: {
...action.payload.termNodes,
},
terms: {
...action.payload.terms,
},
};
And the next one:
case ADD_THINGS:
return {
...state,
...action.payload.shippingProfiles,
};
default:
return state;
The result is that on the first ADD_THINGS, both terms and shippingProfiles data gets put into state correctly, but on the second ADD_THINGS, both shippingProfiles and term data becomes empty.
In redux devtools I see:
ADD_THINGS <--- data is available on both shippingProfiles and terms
ADD_THINGS <--- shippingProfiles and terms data is gone
I don't understand how on the first ADD_THINGS both data gets synced and put into state correclty , and I also don't understand how both data gets erased? Any ideas?

Related

Using setState from inside a reducer. Reducer updates state twice instead of once

I am new to React and I'm trying to build a calculator in React.js. I have a display that contains two fields: one to output the current value and the other one to store a "slice" of the expression. I'm basically trying to immitate the Windows calculator. I use useReducer hook to update state depending on the type of the button pressed. In the dispatch object, I send references to two useState hooks declared inside the App. These hooks are: (1) a useState boolean to indicate whether I am waiting for a second amount, and (2) a useState to save a slice of an expression so it appears in the paragraph above.
The problem is in the reducer function, in the "multiplication_operator" case. When I click on the multiplier sign for the second time, I expect the previous state array ['first amount', 'operator'] to concatenate with the 'second amount'. Basically, I expect an array where index 0 is the first amount, index 1 is the operator and index 2 is the second amount. However, when I try to add the second amount at index 2, it adds the same second amount at the next index (3), which is unexpected. Link to Codesandbox
Link to gif that reproduces the bug
I assume the problem might come from the fact that I use and update too many states from inside the reducer function.
My question is what is the source of the problem and how do I fix it?
Is it the issue of poor choice of hooks or something else? Reducer triggers a chain of updates from App and then back to reducer? I'm really out of ideas here.
It's actually the correct behaviour for useReducer to be called twice. (Details here: https://github.com/facebook/react/issues/16295)
For your app to work as intended despite this, you would have to store/update value, firstNumber and operatorIsActive within the reducer instead of calling setFirstNumber and setOperatorIsActive to update them.
e.g. you could set the initial state of your reducer as
const initialState = {
value: "0",
firstNumber: [],
operatorIsActive: false
};
instead of just storing the initial value.
const initialValue = "0";
In App.js you can access the reducer values this way
const [state, dispatch] = useReducer(displayValueReducer, initialState);
const {value, firstNumber, operatorIsActive} = state;
When the multiply button is clicked, you could access/update the values this way:
case "multiplication_operator": {
const operator = ` ${action.value} `;
if (state.operatorIsActive === false) {
return {
...state,
firstNumber: [state.value, operator],
operatorIsActive: true
};
} else {
return {
...state,
firstNumber: [...state.firstNumber, state.value]
};
}
}
Changes like the above would ensure that your reducer is pure (elaboration here: https://www.geeksforgeeks.org/pure-functions-in-javascript/)

React/Redux: componentDidUpdate not firing consistently

I have nested arrays in my props, and the user can re-order the second-level array by dragging and dropping column. I (using redux) make a shallow copy of getState's outer array, do the re-ordering (through re-assignment of array objects based on index), and return the new array to the state (using spread operator in my reducer). However, componentDidUpdate is not called if this ordering is the first thing I do after page load. If I take other actions that update the props/state, and then attempt to re-order, the ordering works fine and componentDidUpdate is called.
In the former situation, where ordering is the first thing I try to do after page load/refresh, the render function is hit (with the properly re-ordered props) but the DOM never updates, presumably because redux does not recognize a state change?
It just seems like some other props change (even if unrelated to arrays) "wakes up" redux or connect and allows it to recognize changes.
Here's where I copy, dispatch and then release the updated state in the reducer:
in actionCreator method:
...
const newProjects = getState().tracker.projects.slice().map(p => {
if (p.innerArray) {
const from = p.innerArray[draggedId-1];
const to = p.innerArray[droppedId-1];
p.innerArray[draggedId - 1] = { ...to, order: draggedId };
p.innerArray[droppedId - 1] = { ...from, order: droppedId };
}
return p;
})
dispatch({ type: 'RECEIVE_FIELD_UPDATE', projects: newProjects, message: "Sorted" });
reducer:
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: action.projects,
message: action.message
}
As you can see, I'm not mutating state in the reducer or doing other anti-pattern things (as far as I'm aware).
FWIW, the "Sorted" value on message property seems to get through whether or not componentDidUpdate is hit (I have a modal that pops up with the message to confirm).
Any pointers or related documentation would be helpful; I've been reading all I can find, including other StackOverflow posts, but to no avail.
UPDATE
Solved the issue, it did have to do with redux not diff-ing the properties but not because of what I expected, read below if interested.
Figured this out, and per usual I was looking in the wrong place.
The issue was that my conditional check on page load to prevent re-loading of already stored data was preventing any dispatches from firing. When I looked in the reducer, the sub-arrays of the projects were identical after dragging, because the state had not been previously connected yet. I thought a hard refresh would cause a full re-load but apparently I need to read more about how redux stores data and how to refresh that.
Anyways, fixing this condition so that I get new data on page load fixed the issue and componentDidUpdate is now being triggered consistently.
When updating state in a reducer in an array format, I've had to spread state again in order for updates to happen and keep from mutating.
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: ...state, ...action.projects,
message: action.message
}
or...
case 'RECEIVE_FIELD_UPDATE':
return {
...state,
projects: [...state, ...action.projects],
message: action.message
}
Depending on the format.
Hope that helps.

Sending error message from reducer to user

I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.

What are the most important considerations when designing Redux actions?

If I have a situation: a component has 3 operations(list all info || search needed info || filter info), but all of these operations depend on same API, just different parameters.
I'd like to know what is the best approach to design Redux actions? To design 3 actions that are mapping the 3 operations? Or just 1 action because of just 1 API?
What are the most important considerations when designing Redux Actions?
Whether it should be one or two or three actions really depends on how your reducers are organized, and on how your UI is connected to your store.
You should think of API calls as side-effects of your actions, and not as the actions themselves. Your redux store holds a particular state of your UI: what filter is active ? What is the current search query ? and the like...
The most important consideration when designing actions is : what do they mean for the state of my UI? What impact do they have on it ? How do they relate to my store's state ?
If your concern is to mutualize API calls code, there are various patterns to handle that : use a simple helper function taking parameters, use something like redux-saga to trigger side-effects, etc.. But all in all, API calls shouldn't be relevant when designing actions.
There's nothing in the docs that says that actions should map to API endpoints. So you shouldn't have to think of how many API endpoints you have.
From http://redux.js.org/docs/basics/Actions.html:
Actions are payloads of information that send data from your
application to your store.
In your case though, if one action with payload can accomplish the result and mutate your state to the desired condition, I don't see a reason to use three (and thus have three reducers, etc).
The redux docs have a good section on how asynchronous actions/API usage should work within a redux app.
http://redux.js.org/docs/advanced/AsyncActions.html
I've followed this pattern, using the thunk middleware, and found it to work really well. The idea is to give your actions the ability to return a function. You will have:
A "main" function that will dispatch that the application is making an API call, make the API call, and handle the results.
Two pure action creators (functions that return an object with a type and any number of other properties) that will pass the information related to the API call to your reducers.
The actions:
/* Inside ./action.js */
/* This is the function you will dispatch from another component (say, a container component. This takes advantage of thunk middleware. */
export function fetchAllInfo() {
return function(dispatch) {
dispatch(requestAllInfo())
/* Import your API function, make sure it takes a callback as an argument */
API.fetchAllInfo((allinfo) => {
dispatch(receiveAllInfo(allinfo))
}
}
/* Pure action creators */
function requestAllInfo() {
return {
type: "REQUEST_ALL_INFO",
}
}
function receiveAllInfo(allinfo) {
return {
type: "RECEIVE_ALL_INFO",
allinfo,
}
}
The reducer:
/* Inside ./reducer.js */
export function inforeducer(state, action) {
switch(action.type) {
case "REQUEST_ALL_INFO": {
return Object.assign({}, state, {
/* This can be passed as prop so a component knows to show a loader */
isFetching: true,
})
case "RECEIVE_ALL_INFO": {
return Object.assign({}, state, {
isFetching: false,
allInfo: action.allInfo,
})
}
default:
return state
}
}
So with thunk middleware (or your own way of dealing with asynchronous dispatching) and a callback on each API call, you can use this method to work with API calls dispatching at both the beginning of the call and when you receive results.

Should I use one or several action types to represent this async action?

I'm building a front-end for a search system where almost all user actions need to trigger the same async action to re-fetch search results. For example, if a user enters a keyword, then we need to fetch /api/search?q=foo, and if they later select a category we fetch /api/search?q=foo&categoryId=bar. I originally had separate action types for FETCH_RESULTS, SELECT_CATEGORY, DESELECT_CATEGORY, etc. I created one asynchronous action creator for FETCH_RESULTS, but the others are synchronous. The more I think about it, they all end up needing to re-fetching the results from the backend and update the app state based on the response from the backend.
Would it make sense for me to use the single async action-creator for any change? Or would it be better to use async action creators for each distinct user action (selecting a keyword, category, or filter)?
I think the advantage of granular actions would be the events more accurately reflect what the user did (e.g. the user selected a category) vs having to peer into the payload to figure out what actually changed, but they are all pretty similar.
This is of course something only you can really answer based on what you know about the project. I don't think that there is any inherent advantage to having the actions be more granular, and if there aren't any, its not worth the extra effort. I would have a generic FILTER_CHANGED event and not worry about being able to see what specifically changed--presumably the action isn't going to be complicated, so I'm not going to be debugging the action a lot. As the filter state becomes more complicated and diverse, it might make more sense to break out the actions. By default though, I don't really see much value.
I fully agree with Nathan’s answer.
I just want to add that in order to tell whether actions A and B are really one or two actions, you need to ask yourself: “If I change how some reducers react to A, will I also need to change how they react to B?”
When the handlers change together in the reducer code, it’s likely they should be a single action. When their changes may not affect each other, or if many reducers handle just one of them but not the other, they should probably stay separate.
I agree with Dan Abramov: if the text and categories are highly coupled in your interface, just fire FETCH_RESULTS with the text and categories as action payload.
If the text input and categories selection widget do not share a close parent component, it is complicated to fire a FETCH_RESULTS which contains the text and categories (unless passing a lot of props down the tree...): you then need the action granularity.
One pattern that I have found helpful when such granularity is needed is the Saga / Process manager pattern. I've written a bit about it here: https://stackoverflow.com/a/33501899/82609
Basically, implementing this on redux would mean there's a very special kind of reducer that can trigger side-effects. This reducer is not pure, but do not have the purpose of triggering React renderings, but instead manage coordination of components.
Here's an example of how I would implement your usecase:
function triggerSearchWhenFilterChangesSaga(action,state,dispatch) {
var newState = searchFiltersReducer(action,state);
var filtersHaveChanged = (newState !== state);
if ( filtersHaveChanged ) {
triggerSearch(newFiltersState,dispatch)
}
return newState;
}
function searchFiltersReducer(action,state = {text: undefined,categories: []}) {
switch (action.type) {
case SEARCH_TEXT_CHANGED:
return Object.assign({}, state, {text: action.text});
break;
case CATEGORY_SELECTED:
return Object.assign({}, state, {categories: state.categories.concat(action.category) });
break;
case CATEGORY_UNSELECTED:
return Object.assign({}, state, {categories: _.without(state.categories,action.category) });
break;
}
return state;
}
Note if you use any time-traveling (record/replay/undo/redo/whatever) debugger, the saga should always be disabled when replaying actions because you don't want new actions to be dispatched during the replay.
EDIT: in Elm language (from which Redux is inspired) we can perform such effects by "reducing" the effects, and then applying them. See that signature: (state, action) -> (state, Effect)
There is also this long discussion on the subjet.
EDIT:
I did not know before but in Redux action creators can access state. So most problems a Saga is supposed to resolve can often be solved in the action creators (but it creates more unnecessary coupling to UI state):
function selectCategory(category) {
return (dispatch, getState) => {
dispatch({type: "CategorySelected",payload: category});
dispatch({type: "SearchTriggered",payload: getState().filters});
}
}

Resources