Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to update nested props in reducer? #432

Closed
EJIqpEP opened this issue Aug 9, 2015 · 19 comments
Closed

How to update nested props in reducer? #432

EJIqpEP opened this issue Aug 9, 2015 · 19 comments

Comments

@EJIqpEP
Copy link

EJIqpEP commented Aug 9, 2015

I can't figure out the correct way to update nested property in reducers state. When I write return in such was the "data" property is not merged. Or maybe I shouldn't use nested properties in reducers at all?

const initialState = {
  myPosts: {
    isPending: false,
    data: []
  }
};

export default function qual(state = initialState, action) {
  switch (action.type) {
    case 'GET_MY_POSTS_STARTED':
      return {
        ...state,
        myPosts: {
          isPending: true
        }
      };
}

The result is

{myPosts: {isPending: true}}
@svagi
Copy link

svagi commented Aug 9, 2015

With:

{
  ...state,
  myPosts: {
    isPending: true
  }
}

you are replacing whole myPosts object from

const initialState = {
  myPosts: {
    isPending: false,
    data: []
  }
};

For example you can manually merge deep object properties like this:

{
  ...state,
  myPosts: {
    ...state.myPosts,
    isPending: true
  }
}

@EJIqpEP
Copy link
Author

EJIqpEP commented Aug 9, 2015

Thanks for answers. As for me the last variant is the cleanest.

{
  ...state,
  myPosts: {
    ...state.myPosts,
    isPending: true
  }
}

@gaearon gaearon closed this as completed Aug 9, 2015
@noahmatisoff
Copy link

Is this actually how people do this? My reducers are getting very, very lengthy going multiple levels deep.

@markerikson
Copy link
Contributor

markerikson commented Aug 28, 2016

@matisoffn : that's one of the reasons why you are encouraged to keep your Redux state as flat as possible. Writing "manual" nested update code gets really ugly.

FYI, I have a work-in-progress set of new pages for the Redux docs on "Structuring Reducers", and the page on Immutable Update Patterns discusses this a bit.

Overall, the basic approaches to avoid nested object updates are:

  • flatten your state
  • compose reducers more
  • Use utility libraries that abstract out the update steps

As a general FYI, the issue discussing the new pages is at #1784 , and the main WIP page is at https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/StructuringReducers.md .

@noahmatisoff
Copy link

Thanks so much @markerikson

@JimLynchCodes
Copy link

JimLynchCodes commented Dec 30, 2016

Hey guys,
My state is just a regular object. When I try to use this spread operator on the state it gives me the error, "object of type State is not an array type".

screen shot 2016-12-30 at 11 01 22 am

How did you all get around this? thanks.

@markerikson
Copy link
Contributor

@JimTheMan :

First, it looks like you're using TypeScript. TypeScript is going to behave differently than "normal" Javascript.

Second, while the Array Spread operator is part of ES6, the Object Spread operator is still a Stage 3 proposal, and is not yet a final part of the language. The syntax can be enabled if you're using Babel by adding the appropriate compiler plugin. For TypeScript, I think that was just added in TypeScript 2.1.

@mohak1990
Copy link

use babel object spread operator and .babelrc file

@HomeboxJim
Copy link

@mohak1990 Can you use babel and TypeScript together?

@Venryx
Copy link

Venryx commented Mar 16, 2017

@HomeboxJim Yes, you can. Alhough I don't have time to locate a reference, I'm using it currently in multiple projects.

In one, TypeScript compiled to a "JS output" folder, and then babel sees the changes there and then compiles it further using babel.

In another, TypeScript and Babel are both applied as webpack loaders (ts-loader first, then babel) on a set of ts files.

// Loaders
// ------------------------------------
webpackConfig.module.loaders = [
	{
		test: /\.(jsx?|tsx?)$/,
		exclude: [/node_modules/, /react-redux-firebase/],
		loader: "babel",
		query: config.compiler_babel
	},
	{ test: /\.tsx?$/, loader: "ts-loader" },
]

@jackson-sandland
Copy link

jackson-sandland commented Feb 8, 2018

To go even deeper you can do the following:

function updateVeryNestedField(state, action) {
    return {
        ...state,
        first : {
            ...state.first,
            second : {
                ...state.first.second,
                [action.someId] : {
                    ...state.first.second[action.someId],
                    fourth : action.someValue
                }
            }
        }
    }
}

@reduxjs reduxjs deleted a comment from AntaresRahman Feb 22, 2018
@noahtallen
Copy link

noahtallen commented Mar 1, 2018

Going along with this, how would I update a nested dynamic key? For instance, if I have the following:

...
data: {
  key1: {
    key: 'key1',
    name: 'key1'
  },
  key2: {
    key: 'key2',
    name: 'key2'
  }
}

I then perform some update on a key and then have an action coming in with the new key like the following:
data: {key: {key: 'key2', name: 'I changed the name!'}}

In my reducer:

return Object.assign({}, state, {
  ...state,
  data: {
    ...state.data,
    /* how do I dynamically set the key here to change key2 based on the action? */
   needsToBeDynamicKeyHere: action.data.key
  }
})

In my use case, I am dynamically mapping a location to a particular category which is already in the state. (For instance, key2 could be the category that my logic decides to map the location to). Then, I make updates to that particular category. After that, I need to save the category back into the state. However, I don't want to statically type out each category (there are several). I basically need to be able to in the action specify which category to update (e.g. data.key.key), and then the reducer updates the correct category with the new data (e.g. data.key) Is this possible?

@markerikson
Copy link
Contributor

@ntomallen : use an ES6 computed property:

return {
    [someVariableOrExpression] : someValue
};

so in your case:

return {
    ...state,
    data : {
        ...state.data,
        [action.data.key.key] : action.data.key
    }
}

@franciscop-invast
Copy link

Is there any way to do state drilling better? setIn from Immutable.js seems to do the trick but wouldn't like to import the whole library only for this:

const { Map } = require('immutable@4.0.0-rc.9')
const originalMap = Map({
  subObject: Map({
    subKey: 'subvalue',
    subSubObject: Map({
      subSubKey: 'subSubValue'
    })
  })
})

const newMap = originalMap.setIn(['subObject', 'subKey'], 'ha ha!')
// Map {
//   "subObject": Map {
//     "subKey": "ha ha!",
//     "subSubObject": Map { "subSubKey": "subSubValue" }
//   }
// }

@markerikson
Copy link
Contributor

@franciscop-invast : there's dozens of immutable update utility libraries available. I personally would suggest using Immer, as it's the simplest to use.

@Venryx
Copy link

Venryx commented May 11, 2018 via email

@lukemwillis
Copy link

If your state object is a dictionary where the keys are variable Ids instead of known words, you can do this:

export default function reducer(state = {}, action) {
  switch(action.type) {
    case 'UPDATE_SOME':
      return {
        ...state,
        ...Object.keys(action.payload).map(key => ({
          ...state[key],
          ...action.payload[key]
        })
      }
    default: 
      return state;
  }
}

@sandrumirceaioan
Copy link

sandrumirceaioan commented Jul 23, 2021

Ha-dooooo-ken!!!

  on(ContractActions.createExtendedContractsSuccess,
    (state, action) => {
      return {
        ...state,
        fulfillerContracts: {
          ...state.fulfillerContracts,
          entities: {
            ...state.fulfillerContracts.entities,
            [action.payload.groupId]: {
              ...state.fulfillerContracts.entities[action.payload.groupId],
              retailers: [
                ...state.fulfillerContracts.entities[action.payload.groupId].retailers
              ].map(item => {
                if (item.contractId === action.payload._id) {
                  return {
                    ...item,
                    expiryDate: action.payload.expiryDate
                  }
                }
                return {
                  ...item
                }
              })
            }
          }
        }
      }
    }
  ),

...just to update a simple date for a contract...

@markerikson
Copy link
Contributor

@sandrumirceaioan note today we specifically recommend using Immer, which is already built into our official Redux Toolkit package:

and using Immer would drastically simplify that code

@reduxjs reduxjs locked as resolved and limited conversation to collaborators Jul 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests