re-state
Getting Started

Build a state module

As shared state grows, repeating its key and update rules across components becomes hard to maintain. createReStateMethods keeps those details in one module and generates the hooks and helpers your application needs.

Define the state and its actions

This cart module owns the cart key, its initial value, and every allowed update:

import { createReStateMethods } from '@raulpesilva/re-state';

export type CartItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
};

type CartState = {
  items: CartItem[];
};

const initialCart: CartState = {
  items: [],
};

export const { useCart, useCartSelect, dispatchCart, getCart, resetCart } = createReStateMethods('cart', initialCart);

export const addItem = (item: Omit<CartItem, 'quantity'>): void => {
  dispatchCart((currentCart) => {
    const existingItem = currentCart.items.find((currentItem) => currentItem.id === item.id);
    const items = existingItem
      ? currentCart.items.map((currentItem) =>
          currentItem.id === item.id ? { ...currentItem, quantity: currentItem.quantity + 1 } : currentItem
        )
      : [...currentCart.items, { ...item, quantity: 1 }];

    return { items };
  });
};

export const removeItem = (id: string): void => {
  dispatchCart((currentCart) => ({
    items: currentCart.items.filter((item) => item.id !== id),
  }));
};

export const clearCart = (): void => {
  resetCart();
};

Each action receives the current cart and returns a new value. Components do not need to know the state key or repeat the update rules.

Use the module from a component

A component that only reads the cart can use the generated read-only hook:

import { addItem, clearCart, removeItem, useCartSelect } from '../states/cart';

export function Cart() {
  const cart = useCartSelect();
  const total = cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);

  return (
    <section>
      <h2>Cart ({cart.items.length} items)</h2>
      {cart.items.map((item) => (
        <div key={item.id}>
          <span>
            {item.name} × {item.quantity}
          </span>
          <button type="button" onClick={() => removeItem(item.id)}>
            Remove
          </button>
        </div>
      ))}
      <p>Total: {total.toFixed(2)}</p>
      <button type="button" onClick={() => addItem({ id: '1', name: 'Product', price: 10 })}>
        Add product
      </button>
      <button type="button" onClick={clearCart}>
        Clear cart
      </button>
    </section>
  );
}

The total is derived from the items instead of stored separately, so it cannot drift out of sync with the cart.

What the factory creates

For createReStateMethods('cart', initialCart), the returned object contains:

MethodUse it to
useCart()Read and update the cart in a component
useCartSelect()Read the cart in a component without a setter
dispatchCart(value)Update the cart from any code
getCart()Read the latest cart without subscribing
resetCart()Restore initialCart

Keep the factory call at module scope so the state has one clear owner. Prefer the read-only hook in display components and expose actions for domain updates.

If you need custom method names or only one helper, use the individual factories. For persistence or another side effect outside React, subscribe with onReStateChange.

Organize by feature

Start with one state module per feature:

src/
  states/
    cart.ts
    session.ts
    theme.ts
  components/
    Cart.tsx

You can split a module later if it becomes too large. The important part is that components depend on its public hooks and actions, not on duplicated keys or update logic.

On this page