Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleaner/shorter way to update nested state in Redux?

Sometimes reducers get kind of messy:

const initialState = {
    notificationBar: {
        open: false,
    },
};

export default function (state = initialState, action) {
  switch (action.type) {
    case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
      return Object.assign({}, state, {
        // TODO: Find a cleaner way to do this!
        notificationBar: Object.assign({}, state.notificationBar, {
          open: true,
        }),
      });
    default:
      return state;
  }
}

Is there a more terse way to do this?

like image 353
ffxsam Avatar asked Feb 24 '16 02:02

ffxsam


People also ask

How do you update state in Redux?

The only way to update a state inside a store is to dispatch an action and define a reducer function to perform tasks based on the given actions. Once dispatched, the action goes inside the reducer functions which performs the tasks and return the updated state to the store. This is what Redux is all about.

How do I change state in nested objects?

To update nested properties in a state object in React: Pass a function to setState to get access to the current state object. Use the spread syntax (...) to create a shallow copy of the object and the nested properties. Override the properties you need to update.

Can we update state in reducer?

The state is updated and managed by reducers. Reducers always have to return something even if it's null ; they should never return undefined . If a reducer's state is an object, you must always return a new object instead of editing the object in place.

What is state in reducer in Redux?

In Redux, a reducer is a pure function that takes an action and the previous state of the application and returns the new state. The action describes what happened and it is the reducer's job to return the new state based on that action. 1(previousState, action) => newState.


4 Answers

UPD: it's now a part of the ES2018

It might be slightly improved via a non-standardised yet properties spread syntax:

return {
    ...state,
    notificationBar: {
        ...state.notificationBar,
        open: true,
    },
};
like image 176
zerkms Avatar answered Oct 11 '22 15:10

zerkms


Although it's possible to use the spread operator, there are lots of other ways to achieve the same result without even needing a future JS compiler for a non standardised feature. Here are some other options in no particular order.

Return a Literal

If you are sure that your state won't grow, then you can simply return the entire new state as a literal.

return {
  notificationBar: {
    open: true
  }
}

However, that's not often going to be appropriate because it's unlikely that your state will be this simple.


Combine Reducers

Redux gives you a utility method for combining several reducers that work on different parts of the state object. In this case, you'd create a notificationBar reducer that handled this object alone.

 createStore(combineReducers({
   notificationBar: function(state=initialNBarState, action) {
     switch (action.type) {
       case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
         return Object.assign({}, state, { open: true });
   }
 });

This prevents you from having to worry about the top level of properties, so that you can avoid nesting calls to Object.assign.

If your state can logically broken down into clearly defined sections then this is probably the most idiomatic way to solve this problem.


Use Immutable Data

You can use an persistent data structures library to create data structures than can be modified to return a copy.

Mori

Mori is the result of compiling Clojure's data structures and functional API into JS.

import { hashMap, updateIn } from 'mori';

const initialState = hashMap(
  "notificationBar", hashMap(
    "open", false
  )
);

// ...

return updateIn(state, ['notificationBar', 'open'], true);

ImmutableJS

ImmutableJS is a more imperative approach to bringing the semantics of Hash Array Mapped Tries from Clojure's persistent data structures to Javascript.

import { Map } from 'immutable';

const initialState = Map({
  notificationBar: Map({
    open: true
  });
});

// ...

return state.setIn(['notificationBar', 'open'], true);

Alias Object.assign

You can create a friendlier version of Object.assign to write terser versions of the code above. In fact, it can be nearly as terse as the ... operator.

function $set(...objects) {
  return Object.assign({}, ...objects);
}

return $set(state, {
  notificationBar: $set(state.notificationBar, {
    open: true,
  })
});

Use Immutable Helpers

There are a number of libraries that also offer immutability helpers for making modifications to regular mutable objects.

react-addons-update

React has had a built in set of immutability helpers for a long time. They use a similar syntax to MongoDB queries.

import update from 'react-addons-update';

return update(state, {
  notificationBar: {
    open: { $set: true }
  }
});

dot-prop-immutable

This library allows you to use familiar dot paths to specify updates to (nested) properties.

import dotProp from 'dot-prop-immutable';

return dotProp.set(state, 'notificationBar.open', true);

update-in

This library is a wrapper around react-addons-update and provides a more functional syntax for updating (nested) properties.

Rather than passing a new value, you pass a function which takes the old value and returns a new one.

import updateIn from 'update-in';

return updateIn(state, ['notificationBar', 'open'], () => true);

immutable-path

For updating properties, this library is like a cross between dot-prop-immutable and update-in.

import path from 'immutable-path';

return path.map(state, 'notificationBar.open', () => true);
like image 23
Dan Prince Avatar answered Oct 11 '22 14:10

Dan Prince


You can use Lenses.

import { set, makeLenses } from '@DrBoolean/lenses'

const L = makeLenses(['notificationBar', 'open']);
const notificationBarOpen = compose(L.notificationBar, L.open)
const setNotificationBarOpenTrue = set(notificationBarOpen, true)

const a = { notificationBar: { open: false } }
const b = setNotificationBarOpenTrue(a) 
// `a` is left unchanged and `b` is `{ notificationBar: { open: true } }`

You can think about Lenses as compositional property access/update.

Some good resources about Lenses:

  • video Lenses Quick n' Dirty
  • practical article Lenses and Virtual DOM Support Open Closed
  • video Functional programming patterns for the non-mathematician
  • article about combining Lenses with imutable.js

If you are ok with reading lisps, I would also recommend taking look at this excellent introduction to lenses from racket docs. Finally, if you want to go deeper and are ok with reading haskell you can watch: Lenses - compositional data access and manipulation.

like image 33
Safareli Avatar answered Oct 11 '22 14:10

Safareli


If you're using Immutable.js, you can look under Nested Structures topic some functions that may help you, I personally use mergeDeep:

prevState.mergeDeep({ userInfo: {
  username: action.payload.username,
} }),
like image 23
Ricardo Mutti Avatar answered Oct 11 '22 14:10

Ricardo Mutti