Blankman
Blankman

Reputation: 266910

How to correctly change my state in my reducer

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, ????);

Upvotes: 0

Views: 58

Answers (3)

rodgobbi
rodgobbi

Reputation: 1522

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])
...

Upvotes: 1

Anthony
Anthony

Reputation: 6482

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.

Upvotes: 2

Petr Lazarev
Petr Lazarev

Reputation: 3182

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.

Upvotes: 1

Related Questions