For full documentation, with details and examples, see con-estado docs.
npm i con-estado
yarn add con-estado
deno add jsr:@rafde/con-estado
con-estado
is a state management library built on top of Mutative, like Immer but faster,
with the goal of helping with deeply nested state management in your application.
con-estado
?Managing deeply nested state in React often becomes cumbersome with traditional state management solutions. con-estado
provides:
Built on Mutative's efficient immutable updates, con-estado
is particularly useful for applications with:
import { useCon } from 'con-estado'; // Define your initial state const initialState = { user: { name: 'John', preferences: { theme: 'dark', notifications: { email: true, push: false, }, }, }, posts: [], }; function MyComponent() { const [ state, { setWrap, } ] = useCon( initialState, ); return ( <div> <h1> Welcome {state.user.name} </h1> <button onClick={setWrap('user.preferences.notifications.email', (props) => props.draft = !props.stateProp)}> Toggle Email Notifications </button> </div> ); }
For applications needing global state management, createConStore
provides a solution with built-in actions and optimized updates:
import { createConStore } from 'con-estado'; type CounterState = { count: number; } const useSelector = createConStore<CounterState>({ count: 0, }, { acts: ({ set }) => ({ increment() { set(({ draft, }, ) => { draft.count++; }, ); }, asyncIncrement() { return new Promise(resolve => { setTimeout(() => { set('state', ({ draft, }, ) => { draft.count++; }, ); resolve(); }, 100); }); }, incrementBy(amount: number) { set(({ draft, }, ) => { draft.count += amount; }, ); }, }), }); // In component function Counter() { const { count } = useSelector(props => props.state); const { increment, asyncIncrement, incBy5 } = useSelector( ({acts: {increment, asyncIncrement, incrementBy}}) => ({increment, asyncIncrement, incBy5(){ incrementBy(5) }}) ); return ( <div> <h2>Count: {count}</h2> <button onClick={increment}>Increment</button> <button onClick={asyncIncrement}>Async Increment</button> <button onClick={incBy5}> Add 5 </button> </div> ); }
Key advantages:
Local state management.
const [state, controls] = useCon(initialState, options?);
You get the advantages of createConStore
but with local state.
Optimize renders by selecting only needed state:
function UserPreferences() { const preferences = useCon( initialState, { selector: props => ( { theme: props.state.user.preferences.theme, updateTheme: props.setWrap( 'user.preferences.theme', ( props, event: ChangeEvent<HTMLSelectElement>, ) => props.draft = event.target.value, ), } ), } ); return ( <select value={preferences.theme} onChange={preferences.updateTheme} > <option value="light">Light</option> <option value="dark">Dark</option> </select> ); }
Selector is a function that returns the state you need. Only re-renders on non-function changes.
Define reusable actions for complex state updates:
function PostList() { const [state, { acts }] = useCon(initialState, { acts: ({ currySet, wrapSet }) => { // currySet is a function that returns a function that can be called with the posts array const setPost = currySet('posts'); return { addPost(post: Post) { setPost(({ draft }) => { draft.push(post); }); }, updatePost: wrapSet('posts', ({draft}, id: number, updates: Partial<Post>) => { // draft is a mutable object that is relative to the state.posts array const post = draft.find(p => p.id === id); if (post) Object.assign(post, updates); }), async fetchPosts() { const posts = await api.getPosts(); setPost( posts ); } } } }); return ( <div> {state.posts.map(post => ( <PostItem key={post.id} post={post} onUpdate={updates => acts.updatePost(post.id, updates)} /> ))} <button onClick={() => acts.fetchPosts()}> Refresh Posts </button> </div> ); }
Track and access previous state values:
function StateHistory() { const [state, { get, reset }] = useCon(initialState); const history = get(); // Get full state history const previousState = history.priorState; return ( <div> <pre>{JSON.stringify(previousState, null, 2)}</pre> <button onClick={reset}>Reset State</button> </div> ); }
Global store for state management.
const useConSelector = createConStore(initialState, options?); const [state, controls] = useConSelector(selector?);
Local state management.
const [state, controls] = useCon(initialState, options?);
initial
: Record<string | number, unknown>
or Array<unknown>
useCon
can accept a callbackoptions
: Configuration options for createConStore
and useCon
.
acts
: Callback function
for creating the actions object. The action functions can be called with the controls
object.afterChange
: Async callback after state changesmutOptions
: Configuration for mutative
optionstransform
: Callback function
to transform the state
and/or initial
properties before it is set/resetselector
: Custom state selector function that lets you shape what is returned from useCon
and createConStore
set(path, value)
: A function to update state
propertiescurrySet(path)
: Get a function to specify which part of state
you want to update by currying set(path)
setWrap(path, value)
: Lets you wrap the set
function in a function that will be called with the draft value to update.acts
: Custom defined actionsget(path?)
: Get current state or value at pathreset()
: Reset state to initialgetDraft(path?, mutOptions?)
: Get mutable draft of state
and/or initial
propertiessetHistory(path, value)
: A function to update state
and/or initial
propertiescurrySetHistory(path)
: Get a function to specify which part of state
and/or initial
you want to update by currying setHistory(path)
setHistoryWrap(path, value)
: Lets you wrap the setHistory
function in a function that will be called with the draft value to update.con-estado
is written in TypeScript and can infer the state and actions types:
const [state, { set }] = useCon({ user: { name: 'John', preferences: { theme: 'light' as 'light' | 'dark', notifications: { email: true, push: false } } }, posts: [] as string[] });
Add Package
deno add jsr:@rafde/con-estado
Import symbol
import * as con_estado from "@rafde/con-estado";
---- OR ----
Import directly with a jsr specifier
import * as con_estado from "jsr:@rafde/con-estado";
Add Package
npx jsr add @rafde/con-estado
Import symbol
import * as con_estado from "@rafde/con-estado";
Add Package
yarn dlx jsr add @rafde/con-estado
Import symbol
import * as con_estado from "@rafde/con-estado";
Add Package
pnpm dlx jsr add @rafde/con-estado
Import symbol
import * as con_estado from "@rafde/con-estado";
Add Package
bunx jsr add @rafde/con-estado
Import symbol
import * as con_estado from "@rafde/con-estado";