/update_by_path

Allows easy, deep, immutable alterations to objects.

Primary LanguageJavaScriptMIT LicenseMIT

Update by path

Build Status Coverage Status

Intent

It is a function that allows to make immutable alterations to objects. It's meant to reduce the code needed to update a deep value without any mutations. It brings readability by reducing the verbosity.

Installation

As simple as that

npm i --save @madappgang/update-by-path

import update from '@madappgang/update-by-path';

Real-world example

I find this package extremely useful with redux reducers, so I'm going to use one of those as an example.

First off, let's take a look at what a simple reducer would look like without this package.

  const INITIAL_STATE = {
    isFetching: false,
    list: [],
    error: null,
  };

  export default (state = INITIAL_STATE, action) => {
    const { type, payload } = action;

    switch (type) {
      case types.FETCH_ATTEMPT:
        return {
          ...state,
          isFetching: true,
        };
      case types.FETCH_SUCCESS:
        return {
          ...state,
          isFetching: false,
          list: payload,
        };
      default:
        return state;
    }
  };

It can be a lot shorter if we use update from the package

...

case types.FETCH_ATTEMPT:
  return update(state, { isFetching: true });
case types.FETCH_SUCCESS:
  return update(state, { isFetching: false, list: payload });
default:
  return state;

...
};

Same result. What about a bit more complex alterations?

Here we got to replace a list element by id:

...

case types.REPLACE_BY_ID
  return {
    ...state,
    list: state.list.map((element) => {
      if (element.id === payload.id) {
        return payload;
      }
      return element;
    });
  };

...

Let's apply the update function:

...

case types.REPLACE_BY_ID:
  return update(state, `list[id=${payload.id}]`, payload);

...

Still a single line, and still completely immutable.

API

It accepts either 2, or 3 arguments.

update(object, 'deep.path.to.prop', valueToInsert);

update(object, {
  'deep.path.to.props': valueToInsert,
  'another.path': valueToInsert,
});

They are completely identical, though the second option allows to make multiple insertions at a time.

Create new nodes

If the path contains nodes that do not exist in the source object, they are going to be created

update({}, 'deep.path', 'value');
// { deep: { path: 'value' } }

Use function as a value

If you need to generate next value based on the previous one, use a function

const user = {
  name: 'John',
};

update(user, {
  name: name => name.toUpperCase(),
});
// { name: 'JOHN' }

Go deep inside arrays

By value

const user = {
  skills: ['html', 'javascript'],
};

update(user, `skills[javascript]`, 'JavaScript');
// { skills: ['html', 'JavaScript' ] }

By index

const user = {
  skills: ['html', 'javascript'],
};

update(user, `skills[0]`, skill => skill.toUpperCase());
// { skills: ['HTML', 'javascript' ] }

By property value

const user = {
  skills: [{ name: 'html'}, { name: 'javascript' }],
};

update(user, `skills[name=javascript]`, { name: 'JavaScript' });
// { skills: [{ name: 'html'}, { name: 'JavaScript' }] },

If there are no matches for your query the source object remains intact.

Go even deeper inside array elements.

Alter a property of the matching array element. It doesn't metter which kind of query to use to match the element.

const user = {
  skills: [{ name: 'html'}, { name: 'javascript' }],
};

update(user, `skills[name=html].name`, name => name.toUpperCase());
// { skills: [{ name: 'HTML'}, { name: 'javascript' }] },

Contribute

First off, thanks for taking the time to contribute! Now, take a moment to be sure your contributions make sense to everyone else.

LICENSE

This project is licensed under the MIT License - see the LICENSE file for details.