
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
deep-state-manager
Advanced tools
Slimmer than Immer :). As fast as Immer!
Managing state has a lot of shapes. Redux is the most easy to grasp and have state management under control when application starts to get really big. But you need to make custom reducer and invent new name for each action which takes time and complicates workflow. In more than 90% of cases, custom reducer is not needed at all, because we perform really simple transformations.
Deep-state-manager uses simple object like traversing, but with more control what we want to select in state. When we select or better said isolate the part of the state that we want to change, we use trigger words like set, update, delete, prepend, append transformations and provide payload if needed and that is it.
If you need to, you can easily integrate custom reducer on top of it and have access to full Redux experience where you need to.
import React, { useMemo } from 'react'
function SomeComponent() {
return useMemo(()=> {
return (
<>
<div>Will render only on version number change!</div>
</>
)
},[state.some.substate.___version, state.other.substate.___version])
}
//
// 1. use provided state.js file for implementation at the bottom of this readme
//
//
// 2. add these lines to root component
//
import { useGlobal, StateContext } from "./state";
const { state, dispatch } = useGlobal();
function App() {
return (
<>
<StateContext.Provider value={{ state, dispatch }}>
...
</StateContext.Provider>
</>
)
}
//
// 3. add to any child component
//
import { useLocal } from "./state";
const { state, stateRoot, dispatch, dispatchRoot, safe } = useLocal();
// state - local state
// stateRoot - entire state
// dispatch - send actions to local state only
// dispatchRoot - send actions to entire state
// safe escapes path, ie. const x="dont.break.state"; d(safe`some.substate.${x}.substate`);
In this case we need to update all planets to have tracking key present which do not have name deimos. Here is the one-liner implementation you need to write to achieve this (versioning keys are purposefully left out in this minimal example):
let state = {
planets: [
{
name: 'earth',
satellites: [
{ name: 'moon' },
],
},
{
name: 'mars',
satellites: [
{ name: 'phobos' },
{ name: 'deimos' },
],
},
],
}
d(state, `planets..satellites.name!="deimos"#update`, { track: true })
{
planets: [
{
name: 'earth',
satellites: [
{ name: 'moon', track: true }, // UPDATED
],
},
{
name: 'mars',
satellites: [
{ name: 'phobos', track: true }, // UPDATED
{ name: 'deimos' },
],
},
],
}
state = d(state, action, payload?)
The "." (dot) is the separator for the path, it slices the path into unique parts.
Each unique part can be:
// basic set/update/get per key
user.list#set // will overwrite the key
user.list#update // will spread the keys from payload
user.list#del // will delete the key
// array transform basic on user.list = []
user.list#prepend
user.list#append
user.list#...append
user.list#...appendreverse
user.list#...prepend
user.list#...prependreverse
// obj # array ALL elements # obj
user.list..friends..#update
user.list..friends.name="manager"#update
// array of values filtered by inclusion
user.list.=2|=3#del
user.list.=true#del
user.list.="manager"#del
user.list.={"name":"manager"}#del
// array of values filtered by exclusion
user.list.!=2|!=3#del
user.list.!=true#del
user.list.!="manager"#del
user.list.!={"name":"manager"}#del
// obj # array of obj filtered
user.list.id=1|name="manager"#set
user.list.id=3&name="manager"#set
user.list.id=1|name="manager"#update
user.list.id=2&name="manager"#update
user.list.id=1|name="manager"#del
user.list.id=3&name="manager"#del
// obj # array of obj filtered # obj # array of values
user.list.name="manager".list.=2|=3#del
// obj # array of obj filtered # array
user.list.id=1|name="manager".friends#append
user.list.id=1|name="manager".friends#prepend
(trigger can be: append, appendreverse, ...append, ...appendreverse)
(trigger can be: prepend, prependreverse, ...prepend, ...prependreverse)
// obj # array of obj filtered # obj # arr/obj
user.list.id=1|name="manager".friends.="person"#del
user.list.id=1|name="manager".friends.name="person"#del
user.list.id=1|name="manager".friends.name="person"|name="me"#del
user.list.id=1.friends.name="manager"#del
(trigger can be: del, update, set)
// nagative matching for array keys and objects in array
user.list.!=2#del (or update or set)
user.list.id!=1|name="manager"#del
user.list.id=1&name!="manager"#del
(trigger can be: del, update, set)
// go thru object with specific keys, then find object with specific keys in array
company.roles.age=20&salary>=15.friends.name="two"
const { d, safe } = require("deep-state-manager")
let state = {
company: {
___version: 10,
employees: 140,
roles: [
{ role: 'manager' },
{ role: 'ceo' },
],
},
planets: {
list: ['saturn', 'neptune']
}
}
state = d(state, `planets.list#append`, 'mars')
console.log(state)
// OUTPUTS:
// {
// ___version: 1,
// company: {
// ___version: 10,
// employees: 140,
// roles: [
// { role: 'manager' },
// { role: 'ceo' },
// ],
// },
// planets: {
// ___version: 1
// list: ['saturn', 'neptune', 'mars']
// }
// }
const roleName = 'ceo'
state = d(state, safe`company.roles.role="${roleName}"#update`, { email: 'ceo@somefakeceosite.com' })
console.log(state)
// OUTPUTS:
// {
// ___version: 2,
// company: {
// ___version: 11,
// employees: 140,
// roles: [
// { role: 'manager' },
// { role: 'ceo', email: 'ceo@somefakeceosite.com', ___version: 1 },
// ],
// },
// planets: {
// ___version: 1
// list: ['saturn', 'neptune', 'mars']
// }
// }
baseState = d(baseState, safe`todos#append`, {todo: 'tweet about it'})
baseState = d(baseState, safe`data.id=1#del`)
baseState = d(baseState, safe`data.id=2.age#set`, 100)
baseState = immer(baseState, draftState => {
draftState.todos.push({todo: "Tweet about it"})
})
baseState = immer(baseState, draftState => {
draftState.data = draftState.data.filter(dat=>{
if (dat._id === 1) { return false }
return true
})
})
baseState = immer(baseState, draftState => {
draftState.data = draftState.data.map(dat=>{
if (dat._id === 2) { return {...dat, age: 100} }
return dat
})
})
!!! warning: immer will break if you do not set ie. key 'todos' in advance, but deep-state-manager will create that path for you automagically.
!!! information: in github repo you have copy-paste examples that you can run on runkit and compare immer and deep-state-manager performance yourself
If you need more control, you can prepare the payload before sending it to d() function. Function d() can hit anything in the state with simple path.
Without preparing the payload in advance you can do a lot of transformations automatically with proper path. Most used cases are covered and tested.
Info: A lot of code is there to beautify the final result, so it is more visible. You will be able to track changes in state realtime to get good feeling how exactly deep-state-manager works.
make sure you have node.js installed properly
create react app with by running in terminal:
npx create-react-app app1
cd app1
npm install --save deep-state-manager get-value
open VSCODE, ATOM, WEBSTORM, SUBLIME, ... in the root of /app1
src/state.js
import { createContext, useContext, useReducer } from 'react';
import { d, safe } from 'deep-state-manager';
import getValue from 'get-value'
export const initial = {
___version: 0
};
export const StateContext = createContext(initial);
export function useGlobal() {
const [state, dispatch] = useReducer((globalState, globalAction) => {
const [path, payload] = globalAction;
return d(globalState, path, payload);
}, initial);
return { state, dispatch };
}
export function useLocal(substate) {
const { state: stateRoot, dispatch: dispatchRoot } = useContext(StateContext);
const dispatch = action => value => {
const path = `${(substate && `${substate}.`) || ''}${action}`;
const payload = value && value.target ? value.target.value || '' : value; // event or value
dispatchRoot([path, payload]);
};
const state = getValue(stateRoot, substate)
return { state: state || { ___version: 0 }, stateRoot, dispatch, dispatchRoot, safe };
}
src/App.js
import React from 'react';
import { useGlobal, StateContext } from './state';
import TodoComponent from "./todo-component";
import ExcelComponent from "./excel-component";
function App() {
const { state, dispatch } = useGlobal(); // create state
return (
<>
<StateContext.Provider value={{ state, dispatch }}>
<div style={{ padding: 20 }}>
<h1 style={{ textAlign: 'center', paddingBottom: 20 }}>Deep state manager testgrounds</h1>
<TodoComponent substate='public.user' />
<TodoComponent substate='company.somename.management' />
<ExcelComponent substate="public.calculations" columns="2" rows="3" />
<ExcelComponent substate="company.wide.calculations" />
<pre style={{ backgroundColor: '#333', color: 'white', padding: 10}}>
STATE:
{JSON.stringify(state, null, 2)}
</pre>
</div>
</StateContext.Provider>
</>
);
}
export default App;
src/todo-component.js
import React, { useMemo } from 'react';
import { useLocal } from './state';
function TodoComponent(props) {
const { state, dispatch, safe } = useLocal(props.substate); // substate isolation in motion
return useMemo(() => {
console.log({ RENDERING_COMPONENT: `<TodoComponent substate='state${props.substate ? `.${props.substate}` : ''}' />` })
return (
<>
<div style={{ marginTop: 20, padding: 10, backgroundColor: '#eee' }}>
<TodoComponent substate='state{props.substate ? `.${props.substate}` : ''}' />
<br /><br />
<input
value={state.new || ''}
onChange={e => {
dispatch(`new#set`)(e.target.value);
}}
/>
<button
type="button"
onClick={() => {
dispatch(`list#prepend`)({ name: state.new, key: +new Date() });
dispatch(`new#del`)();
}}
>
Add new todo item
</button>
{state &&
state.list &&
state.list.map(item => (
<div key={item.key}>
<button type="button" onClick={dispatch(safe`list.key=${item.key}#del`)}>
Delete
</button>
{' '}{item.name}
</div>
))}
</div>
</>
)
}, [state.___version])
}
export default TodoComponent;
src/excel-component.js
import React, { useMemo } from "react";
import { useLocal } from "./state";
function ExcelComponent(props) {
const { state, dispatch, safe } = useLocal(props.substate); // substate isolation in motion
return useMemo(() => {
console.log({
RENDERING_COMPONENT: `<ExcelComponent substate='state${
props.substate ? `.${props.substate}` : ""
}' />`
});
return (
<>
<div style={{ marginTop: 20, padding: 10, backgroundColor: "#eee" }}>
<button
onClick={() => {
dispatch(safe`tables#set`)({
data: Array.from(Array(Number(props.rows || 5)).keys()).map(
rowNumber => {
return Array.from(
Array(Number(props.columns || 5)).keys()
).reduce((av, columnNumber) => {
return {
...av,
[`R${rowNumber}C${columnNumber}`]: ""
};
}, {});
}
)
});
}}
>
Create table ({props.columns || 5}x{props.rows || 5})
</button>{" "}
<ExcelComponent substate='state
{props.substate ? `.${props.substate}` : ""}' />
{state &&
state.tables &&
state.tables.data &&
state.tables.data.map(row => (
<div>
{Object.keys(row)
.filter(key => !["id", "___version"].includes(key))
.map(key => (
<input
type="text"
value={row[key] || ""}
onChange={e => {
dispatch(`tables.data.${key}="${row[key]}".${key}#set`)(
e.target.value
);
console.log(
`tables.data.${key}="${row[key]}".${key}#set`
);
}}
/>
))}
</div>
))}
</div>
</>
);
}, [state.___version]);
}
export default ExcelComponent;
run the app and look how app works and state changes in realtime
npm start
FAQs
Slimmer than Immer :). As fast as Immer!
The npm package deep-state-manager receives a total of 0 weekly downloads. As such, deep-state-manager popularity was classified as not popular.
We found that deep-state-manager demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.