Skip to content
usefy

useObjectState

State & data

Object state with immutable partial updates (patch/merge) and reset

Install

$ npm install @usefy/use-object-state

Quick start

useObjectState.tsx
import { useObjectState } from "@usefy/use-object-state";

interface FormState {
  name: string;
  email: string;
  subscribe: boolean;
}

function SignupForm() {
  const [form, patch, reset] = useObjectState<FormState>({
    name: "",
    email: "",
    subscribe: false,
  });

  return (
    <form>
      <input
        value={form.name}
        onChange={(e) => patch({ name: e.target.value })}
      />
      <input
        value={form.email}
        onChange={(e) => patch({ email: e.target.value })}
      />
      <label>
        <input
          type="checkbox"
          checked={form.subscribe}
          onChange={(e) => patch({ subscribe: e.target.checked })}
        />
        Subscribe
      </label>
      <button type="button" onClick={() => reset()}>
        Reset
      </button>
    </form>
  );
}

API reference

useObjectState<T extends object>(initialState)

Returns a [state, patch, reset] tuple.

Parameters

ParameterTypeDescription
initialStateT | (() => T)The initial object, or a factory returning it (evaluated once on mount, then cached)

T must be a plain object/record — this hook is for objects, not arrays (use useList) or primitives (use useState).

Returns [state, patch, reset]

ItemTypeDescription
stateTThe current object
patchObjectStatePatch<T>Immutably shallow-merge a Partial<T> (or a functional updater)
resetObjectStateReset<T>Restore the initial value, or set a provided object

patch(partial | updater)

patch({ field: newValue });                 // shallow-merge a partial
patch((prev) => ({ count: prev.count + 1 })); // compute the patch from prev
  • Accepts a Partial<T> and shallow-merges it immutably: { ...prev, ...partial }. It always produces a new object; the previous state is never mutated.
  • Also accepts a functional updater (prev: T) => Partial<T> for when the next value depends on the current one.
  • Only the provided keys change; untouched keys are preserved by reference.
  • Every patch triggers a re-render — there is no shallow-equality dedupe (matching react-use's useSetState). This keeps the semantics simple and predictable; add your own guard if you need to skip no-ops.

reset(next?)

reset();              // back to the initial state captured on mount
reset(nextState);     // set to the provided object instead
  • reset() restores the value captured on mount. If a lazy initializer was used, the value it produced is cached once and reused — the initializer is not re-run.
  • reset(nextState) replaces the state with the provided object.

Shallow-merge caveat

The merge is shallow — a nested object in the patch replaces the previous nested object wholesale, it is not deep-merged:

const [state, patch] = useObjectState({ user: { name: "Alice", age: 30 } });
patch({ user: { name: "Bob" } });
// state.user is now { name: "Bob" } — `age` is gone.

// Spread the nested object yourself to update one nested field:
patch({ user: { ...state.user, name: "Bob" } });

Types

import {
  useObjectState,
  type ObjectStateInitializer,
  type ObjectStatePatch,
  type ObjectStateReset,
  type UseObjectStateReturn,
} from "@usefy/use-object-state";

Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in state & data