Skip to main content

Built and signed on GitHub Actions

Deeply nested state manager for ReactJS

This package works with Node.js, Deno, BrowsersIt is unknown whether this package works with Cloudflare Workers, Bun
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
It is unknown whether this package works with Bun
This package works with Browsers
JSR Score
100%
Published
19 hours ago (0.2.3)

con-estado

NPM License NPM Version JSR Version Test

Docs

For full documentation, with details and examples, see con-estado docs.

Installation

npm i con-estado
yarn add con-estado
deno add jsr:@rafde/con-estado

Introduction

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.

Why Use con-estado?

Managing deeply nested state in React often becomes cumbersome with traditional state management solutions. con-estado provides:

  • Direct path updates - Modify nested properties using dot notation instead of spreading multiple levels
  • Referential stability - Only modified portions of state create new references, preventing unnecessary re-renders
  • Zero boilerplate actions - Built-in atomic mutations with automatic action creation
  • Optimized selectors - Prevent component re-renders by selecting only relevant state fragments
  • Type-safe mutations - Full TypeScript support for state paths and updates

Built on Mutative's efficient immutable updates, con-estado is particularly useful for applications with:

  • Complex nested state structures
  • Performance-sensitive state operations
  • Frequent partial state updates
  • Teams wanting to reduce state management boilerplate

Basic Usage

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>
	);
}

Global Store

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:

  • Global state accessible across components
  • Pre-bound actions with type-safe parameters
  • Async action support with automatic state updates
  • Optimized subscriptions through selector-based consumption

Local State

Local state management.

const [state, controls] = useCon(initialState, options?);

You get the advantages of createConStore but with local state.

Advanced Usage

Custom Selectors

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.

Actions

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>
	);
}

State History

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>
	);
}

API Reference

createConStore

Global store for state management.

const useConSelector = createConStore(initialState, options?);

const [state, controls] = useConSelector(selector?);

useCon

Local state management.

const [state, controls] = useCon(initialState, options?);

createConStore and useCon Options

  1. initial: Record<string | number, unknown> or Array<unknown>
    • useCon can accept a callback
  2. options: 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 changes
    • mutOptions: Configuration for mutative options
    • transform: Callback function to transform the state and/or initial properties before it is set/reset
  3. selector: Custom state selector function that lets you shape what is returned from useCon and createConStore

createConStore and useCon Controls

  • set(path, value): A function to update state properties
  • currySet(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 actions
  • get(path?): Get current state or value at path
  • reset(): Reset state to initial
  • getDraft(path?, mutOptions?): Get mutable draft of state and/or initial properties
  • setHistory(path, value): A function to update state and/or initial properties
  • currySetHistory(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.

TypeScript Support

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[]
});

Credits to

  • Mutative for efficient immutable updates
  • Immer for inspiring Mutative
  • Zustand for the inspiration
  • Øivind Loe for questioning why I wanted to create a state management library.
Built and signed on
GitHub Actions
View transparency log

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";