npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

modify-via-query

v1.0.7

Published

Modify an object using query without mutating the original object.

Downloads

9

Readme

modify-via-query

Mutate a copy of data without changing the original source with natural and type-safe query.

Why use this library?

This is for users that are sick of updating large states. For example:

setState((state) => ({
  ...state,
  book: {
    ...state.book,
    author: {
      ...state.book.author,
      nickNames: state.book.author.map((name, index) =>
        index === targetIndex ? "new name" : name
      ),
    },
  },
}));

With this library, the code above can be simplified as:

setState(
  modify((state) => state
    .book
    .author
    .nickNames[targetIndex]
    .$set("new name")
  )
);

How to install?

Node.js

npm install modify-via-query --save
import {modify} from "modify-via-query"

Deno

import { modify } from "https://raw.githubusercontent.com/wongjiahau/modify-via-query/master/mod.ts";

Comparison with immutability-helper

Using immutability-helper, taken from this issue:

update(state, {
  objects: {
    [resource]: {
      [id]: {
        relationships: {
          [action.relationship]: {
            data: {
              $apply: data => {
                const { id, type } = response.data;
                const ref = { id, type };
                return data == null ? [ref] : [...data, ref];
              }
            }
          }
        }
      }
    }
  }
});

Using modify-via-query:

modify(state => state
  .objects[resource][id]
  .relationships[action.relationship]
  .data
  .$apply(data => {
    const { id, type } = response.data;
    const ref = { id, type };
    return data == null ? [ref] : [...data, ref];
  })
)(state)

Features

  • Type-safe
  • Autocomplete via Intellisense
  • Chainable
  • Immutable
  • Shallow copy

Main concept

Like the name of this package, you modify by querying the property. The modify function make the object modifiable. A modifiable object comes with a few commands like $set and $apply.
Basically, the commands can be access in any hierarchy of the object, and once the command is invoked, an updated modifiable object will be returned, such that more modifications can be chained.

Examples

Modify object

modify(state => state.x.$set(3))({ x: 2 }) // {x: 3}

Modify array item

modify(state => state[0].$set(3))([1, 2]) // [3, 2]

Modify nested object array

modify(state => state.todos[0].done.$apply(done => !done))({
  todos: [
    {content: "code", done: false},
    {content: "sleep", done: false},
  ]
})
// {todos: [{content: "code", done: true}, {content: "sleep", done: false}]}

Chaining commands

modify(state => state
  .name.$apply(name => name + " " + "squarepants")
  .job.at.$set("Krabby Patty")
)({
  name: "spongebob",
  job: {
    title: "chef"
    at: undefined
  }
})
// { name: "spongebob squarepants", job: {title: "chef", at: "Krabby Patty"} }

Removing array item

modify(state => state.filter((_, index) => index !== 2))(
  ["a", "b", "c"]
)
// ["a", "b"]

Modify property of optional object

For example, if you have the following state:

const state: {
  pet?: {
    name: string
    age?: number
  }
} = {}

Let say you want to update pet.age, you cannot do this:

modify(state => state.pet.age.$set(9))(state)

You will get compile-error by doing so. The is prohibited in order to maintain the type consistency, else the resulting value would be {pet: {age: 9}}, which breaks the type of state, because name should be present.

To fix this, you have to provide a default value for pet using the $default command:

modify(state => state.pet.$default({name: "bibi"}).age.$set(9))(state)

This tells the library that if pet is undefined, then its name will be "bibi" otherwise the original name will be used.

Available commands

  • $set
    • to set the value of the queried property
  • $apply
    • to update the value of the queried property based on its previous value
  • $default
    • to provide a default value if the queried property is a nullable object

Usage with React

Function components (useState hook)

const Counter = () => {
  const [state, setState] = React.useState({count: 0})
  const add = () => setState(modify(state => state.count.$apply(x => x + 1)))
  const minus = () => setState(modify(state => state.count.$apply(x => x - 1)))
  return (...)
}

Class components

class Counter extends React.Component<{}, {count: 0}> {
  constructor(props) => {
    super(props)
    this.state = {count: 0}
  }
  add = () => {
    this.setState(modify(state => state.count.$apply(x => x + 1)))
  }
  minus = () => {
    this.setState(modify(state => state.count.$apply(x => x - 1)))
  }
}

Redux reducers

type State = {count: 0}
type Action = {type: 'add'} | {type: 'minus'}
const myReducer = (state: State, action: Action): State => {
  return modify(state)(state => {
    switch(action.type) {
      case 'add':
        return state.count.$apply(x => x + 1)
      
      case 'minus':
        return state.count.$apply(x => x - 1)
    }
  })
}

Can I use this library in non-React projects?

Yes. Although this library is primarily for users who uses React users, this package can actually be used anywhere since it has zero dependency.

Can I use this library with Typescript?

Yes! In fact the default package already contain the type definitions, so you don't have to install it somewhere else.

How this library works?

It works by using Proxy

Overloads

The modify function is overloaded with two signatures. If you are using React, the first variant will be more convenient. Note that both of the variants can be curried.

// Update -> State -> State
modify: (update: (state: Modifiable<State>) => Modifiable<State>) 
  => (state: State) 
  => State;

// State -> Update -> State
modify: (state: State) 
  => (update: (state: Modifiable<State>) => Modifiable<State>) 
  => State;

References

This library is inspired by: