How to correctly change my state in my reducer - reactjs

So I have an action that is fetching from an API.
I am fetching the user from my API, and it returns the JSON with and returns with the action and the json values for the user and a progress value.
fetchUser: (userId) => {
return dispatch => {
dispatch({ type: Constants.USER_FETCHING });
let url = 'http://localhost:4001/api/v1/users/'+ userId;
axios.get(url)
.then(function(data) {
console.log('data returned is ' + JSON.stringify(data));
dispatch({
type: Constants.GET_USER,
user: data.user,
progress: data.progress,
});
});
};
},
Now in my reducer I have to return the state without mutating it:
import Constants from '../constants';
const initialState = {
users: [],
user: null,
progress: null,
fetching: false,
};
const users = (state = initialState, action) => {
case Constants.USER_FETCHING:
return {...state, fetching: true};
switch (action.type) {
case Constants.GET_USER:
return ?????;
default:
return state;
}
};
export default users;
How exactly should I be returning the state?
I see examples using Object.assign but not sure how to structure it for my case:
return Object.assign({}, state, ????);

You can use the spread operator ... as you did in your USER_FETCHING:
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress
};
This creates a new object by first setting the same properties as they currently are on state, and then overwrites the user and progress properties with the new values from the action.

Both Karbaman and Tony answers are correct. Furthermore, the Object spread is compiled to Object.assign by default if you're using babel, as you can read in the documentation.
To add to those answers, if you want to update the users array, you can use the spread transform (documentation):
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress,
users: [ ...state.users, action.user ]
};
Which will create a new array for users, concat it with the existing one, then with the new user.
However, if the user in the action is already within the existing array, it will get duplicated. You can implement a verification to avoid that, or use directly union from lodash (if user is a String or Number):
....
users: union(state.users, [action.user])
...

If you are going to use Object.assign, then it will be:
return Object.assign({}, state, {user: action.user, progress: action.progress});
How it works:
Object.assign gets 3 objects:
{} - empty
state
{user: action.user, progress: action.progress}
and merges them into 1 object one by one.
It is important to have empty object ( {} ) as a first argument, because in this case props/values from state will be merged to empty object and you will have a new copy.
If you remove empty object or put state as a first argument - in this case everything will be merged to state and you will have a mutation instead of copying.

Related

When using useReducer to control a form, do I pass the previous state on every action?

My form has two inputs:
username (type="text")
email (type="email")
I also have a .json file as a "database" to do some validation. The submit button can only be clicked if the user enters a username that doesn't exist on the .json file. The thing is I don't know if I should pass the "...state" object on every action.type. I am getting some bugs that seem to go away when I use the "...state", but I don't understand why nor if I should always use it.
Here's my code:
const formReducer = (state, action) => {
switch (action.type) {
case "USERNAME_INPUT":
return {
...state,
usernameValue: action.payload,
};
case "API_RETURN_USERNAME":
return {
...state,
usernameValue: action.payload.match
? action.payload.username
: "",
usernameIsValid: !action.payload.match,
emailValue: action.payload.match ? action.payload.email : "",
emailIsValid: !action.payload.email,
apiReturned: true,
};
case "EMAIL_INPUT":
return {
...state,
emailValue: action.payload.value,
emailIsValid: action.payload.isValid,
formIsValid: action.payload.isValid && state.usernameIsValid,
apiReturned: false,
};
default:
return {
usernameValue: "",
emailValue: "",
usernameIsValid: false,
emailIsValid: false,
formIsValid: false,
apiReturned: false,
};
}
The reducer is always called with 2 arguments:
The previous state value and
The action (most often, an object)
The role of reducer function is to compute on these 2 values and generate the next value of state(read a new state object) and discard old state
All action may not change all keys of your state which is typically, and even in this particular case, a object.
We may only want to operate on a few keys of our state object.
So, we have two options, either manually copy all keys or use ES6 spread operator to spread the old state object and then overwrite the keys we want to change with the new value. This will preserve the keys we are not changing.
If you don't take either path, your state will become the object with only the keys you update and hence you may face unexpected behaviour
You should just about always shallow copy the previous state when updating a React state. Think of the useReducer as a very specialized version of the useState hook. Instead of returning the state and an updater function, i.e. [state, setState], it returns an array of the state and a dispatch function, i.e. [state, dispatch]. Please recall that state updates in React function components do not merge updates, so failing to copy any of the previous state will result in it not being included in the next state value, as if it were removed.
Functional Updates
Note
Unlike the setState method found in class components, useState
does not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Another option is useReducer, which is more suited for managing
state objects that contain multiple sub-values.
From this basic understanding of state updates in function components, or if you are already familiar with "legacy" Redux reducer functions, it is trivial to see and understand why the previous state is copied forward.
Note also the default case typically doesn't have any effect on the state, it just returns the current state value.
The single exception I can think of is when you wanting to set the state to a completely new value like resetting back to initial state.
Example:
const formReducer = (state, action) => {
switch (action.type) {
case "USERNAME_INPUT":
return {
...state,
usernameValue: action.payload,
};
case "API_RETURN_USERNAME":
return {
...state,
usernameValue: action.payload.match
? action.payload.username
: "",
usernameIsValid: !action.payload.match,
emailValue: action.payload.match ? action.payload.email : "",
emailIsValid: !action.payload.email,
apiReturned: true,
};
case "EMAIL_INPUT":
return {
...state,
emailValue: action.payload.value,
emailIsValid: action.payload.isValid,
formIsValid: action.payload.isValid && state.usernameIsValid,
apiReturned: false,
};
case "RESET":
return action.payload;
default:
return state;
}
};
...
const initialState = {
usernameValue: "",
emailValue: "",
usernameIsValid: false,
emailIsValid: false,
formIsValid: false,
apiReturned: false,
}
...
const [state, dispatch] = useReducer(reducer, initialState);
...
const resetState = () => {
dispatch("RESET", { payload: initialState });
};

best way to handle fetching Status in redux

I'm looking for the best way to handle my fetching status in my app,
the simplest way is to create an isFetching[actionName] for each action and then the state will look something like this:
state:{
todos:[...],
user[...],
isTodosFetching:true/false,
isUserFetching:true/false
}
but I'm looking for a more elegant way of storing fetching statuses in the store.
so I tried to think of an alternative way and thought about create a fetchingActionsReducer that will add each fetching action to a dict(object) in the store and then the state will look like this:
todos:[...],
user[...],
loadingTasks:{
isTodosFetching:true/false,
isUserFetching:true/false
}}```
now every component will get loadingTasks with mapStateToProps and that's it.
this will reduce the boilerplate to one simple reducer and one action.
reducer:
export const loadingTasks = (state = {}, action) => {
switch (action.type) {
case START_LOADING_TASK:
return Object.assign({}, state, { [action.payload]: true });
case END_LOADING_TASK:
return Object.assign({}, state, { [action.payload]: false });
default:
return state;
}
};
actions:
export const startLoadingTask = (taskName) => ({
type: START_LOADING_TASK,
payload: taskName,
});
export const endLoadingTask = (taskName) => ({
type: END_LOADING_TASK,
payload: taskName,
});```
I tried it it works perfect but I would like to know,
1. there is any better way to handle fetching status with redux?
2. now many portfolios will be subscribed to the loadingTasks state and I'm afraid it will cause performance issues. (for every change in the loading tasks all react will run the digging algorithm for all the components subscribed)
I suggest co-locating the fetching status with the resource being requested, for example:
state:{
todos: {
isFetching: true, // or false
data: [/* ... */]
},
user: {
isFetching: true, // or false
data: [/* ... */]
}
}
This way when the fetching status of todos change only the components dependant on todos will rerender.
This approach also enables additional statuses.
For example if the todos request fails you could have an error status. Or if the user request fails an error message providing some context, or even containing the error returned from the API:
state:{
todos: {
isFetching: false,
hasError: true, // or false
data: [/* ... */]
},
user: {
isFetching: false,
errorMessage: 'Please check username exists', // or null
data: [/* ... */]
}
}

Redux reducer - modifying array with forEach

Have a small problem with fetching and based on response updating an array inside my state in Redux.
First I have done the whole array update with forEach in actions (based on my initial state object) and sent it ready to reducer, it worked. Simple.
But then read tutorials that modifying should be done only in the reducer, and that action should only deal with getting the response. So I have tried doing it this way, two ways, both failed.
The payload i have dispatched to reducer in both cases was just the ready response i have got.
Can someone please enlighten me what went wrong and what's the correct way to do this in reducer?
Both approaches didn't work:
export const handleMusicCards = (state = musicState, action = {}) => {
switch (action.type) {
case REQUEST_MUSIC_SUCCESS:
return Object.assign({}, state, {
musicStateItemList: state.musicStateItemList
.forEach((el, i) => {
el.track = action.payload.message.body.track_list[i].track.track_name;
el.album = action.payload.body.track_list[i].track.album_name;
el.artist = action.payload.body.track_list[i].track.artist_name;
el.id = action.payload.body.track_list[i].track.track_id;
el.favClicked = false;
el.addedToFav = false;
}),
isLoading: false
});
}
}
export const handleMusicCards = (state = musicState, action = {}) => {
switch (action.type) {
case REQUEST_MUSIC_SUCCESS:
return Object.assign({}, state, {
musicStateItemList: state.musicStateItemList
.forEach((el, i) => {
return {
...el,
track: action.payload.message.body.track_list[i].track.track_name,
album: action.payload.message.body.track_list[i].track.album_name,
artist: action.payload.message.body.track_list[i].track.artist_name,
id: action.payload.message.body.track_list[i].track.track_id,
favClicked: false,
addedToFav: false,
}
}),
isLoading: false
});
}
}
I am not sure after reading it where the failure is occurring. A little more about redux conventions.
The action objects are only to describe what changes should be made to the state. The reducer is where the state should actually be changed. In redux, you never want to modify the state object, instead you want to copy it and return a new object with the changes, as described by the action objects.
So you might have a reducer case that looks something like this...
const reducer = (state, action) => {
switch (action.type) {
case NEW_RECORD_SUBMIT :
return {
...state,
newRecordStatus: action.status,
};
default :
return state;
}
};
It's solved now. Very silly mistake, wrong case in switch statement...Went for the second option I tried, with map()

How to Better Handle Error and Updates in Simple React-Redux Reducer

I've been reading about normalizing state that gets returned from my reducer (React-redux) and I think I understand that if I don't spread (...) the state returned, that my React app will not recognize that a state change has happened and my UI will not update.
That is, if I return some thing like:
data: action.payload.data // where this is an array
instead of what I have below
...action.payload.data
My UI does not update (no render() called).
Assuming this is correct (which my testing seems to show), then I'm perplexed on how I can return an error condition. What I've done below is returned isLoading and isErrored as properties in place of the single array property.
This feels ugly. Like I'm using my return to mean one thing on success and another thing on errors.
Suggestions for a better way to do it? What am I missing?
export function sessionAttendee(state = {}, action) {
switch (action.type) {
case SESSIONATTENDEE_LOAD: {
return Object.assign({}, state, {
isLoading: true,
hasErrored: false
});
}
case SESSIONATTENDEE_LOAD_SUCCESS: {
return Object.assign({}, state, {
...action.payload.data
});
}
case SESSIONATTENDEE_LOAD_FAIL: {
console.log("SESSIONATTENDEE_LOAD_FAIL");
return Object.assign({}, state, {
isLoading: false,
hasErrored: true,
errorMessage: action.error.message
});
}
Object.assign returns a new object. This will ensure that you don't mutate the previous state. There's plenty of good reasons in this GitHub issue as to why you want to avoid mutating your state.
In regards to your statement "This feels ugly. Like I'm using my return to mean one thing on success and another thing on errors" I would say that you aren't actually using your returns to do multiple things. They always return the state object for that reducer. All that is different in the switch statements is that you're updating properties of that object.
FWIW, I've noticed that you are spreading within an Object.assign which is probably unnecessary. Here's how I would re-write your reducer so it would make more sense to me...
const initialState = {
isLoading: false,
hasErrored: false,
errorMessage: null
data: [],
}
export function sessionAttendee(state = initialState, action) {
switch (action.type) {
case SESSIONATTENDEE_LOAD: {
return {
...state,
isLoading: true,
hasErrored: false
}
}
case SESSIONATTENDEE_LOAD_SUCCESS: {
return {
...state,
data: action.payload.data
}
}
case SESSIONATTENDEE_LOAD_FAIL: {
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: action.error.message
}
}
default:
return state;
}
}
defined an initialState as that makes it somewhat easier to reason when reading. Not a requirement, but I find it helps.
switched from Object.asssign to using the ES6 spread operator.
Ensured that your action.payload.data key was actually assigned to a property on the state object, rather than just smishing it in.

Generate reducers dynamically (universal reducer for api calls)

In redux how can i make reducers dynamically based on api call passed as string to an action-creator to reduce the boilerplate (so for each api call there was a dedicated key inside the store)?
And should i even try to do that?
Example jsfiddle
The problem is here:
export function universalFetchReducer(state = initialState, action) {
switch (action.type) {
case 'FETCHING_DATA' + action.metadata:
return {
...state,
isFetching: true
};
case 'FETCHING_DATA_SUCCESS' + action.metadata:
return {
...state,
isFetching: false,
data: action.data,
dataFetched: true
};
case 'FETCHING_DATA_FAILURE' + action.metadata:
return {
...state,
isFetching: false,
error: true
};
default:
return state;
}
}
For now i can create actions and their names based on url passed to an action-creator, but cannot make a dedicated reducer.
Solved this by using redux-injector, followed its api to create an action creators and a simple async action creator (axios used):
export function getData(api) {
return {
type: `FETCHING_DATA_${api}`,
meta: api
}
}
export function universalFetchData(api) {
injectReducer(`universalFetch${api}`, universalFetchReducer);
return dispatch => {
dispatch(getData(api)) //Some initial action. Pass api to name actions
axios
.get(api)
.then(response => {
dispatch(getDataSuccess(response.data, api)) //Some success action
})
.catch(error => getDataFailure(error.response.status, api)) } } //Some failure action
Then just fired an universalFetchData('path_to_api') from component and got FETCHING_DATA_path_to_api action in redux-devtools.
Got data from store
state.universalFetchReducer_path_to_api
and passed this state to render with e.g. ramda's pathOr to set unkown initial state.
Lesson learned: you will be able to make many simple lazy loading api calls fast, but do this only if you know what data you're getting. For more dangerous logic use regular reducers upfront. This solution nowhere near acceptable but it gets job done.

Resources