npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

use-immer-observable

v1.1.2

Published

React hook for immutable state updates with Immer and Proxy observable

Readme

use-immer-observable

React hook for immutable state updates with Immer and Proxy observable.


Features

  • Uses Immer for immutable state updates
  • Tracks deep nested changes: all property updates are detected by a Proxy and then passed to Immer for immutable state updates
  • Simple API to work with reactive immutable state
  • Batch updates (proxy.batch) allow you to group multiple changes into a single render.

🛠️ How It Works

useImmerObservable combines Immer and JavaScript Proxy to provide intuitive, immutable state management with a mutable-like API.

  • The state object is wrapped with a Proxy, so you can update properties directly (e.g., proxy.set.xxx = ...).
  • All changes are intercepted by the Proxy and applied immutably using Immer under the hood.
  • React's useState is used to trigger re-renders when the state changes.

Architecture Diagram

[Your Component Code]
       |
       v
   [Proxy Wrapper] --(change detection)--> [Immer produce] --(new state)--> [React useState] --(re-render)

Installation

npm install use-immer-observable

or

yarn add use-immer-observable

Usage

import useImmerObservable from "use-immer-observable";

const initialState = {
  user: { name: "Alice", age: 25 },
  items: [1, 2, 3],
};

const MyComponent = () => {
  const [state, proxy] = useImmerObservable(initialState);

  // Update nested property
  proxy.set.user.name = "Bob";

  // Replace the entire state object
  proxy.set = {
    user: { name: "Charlie", age: 30 },
    items: [4, 5, 6],
  };

  return <div>{state.user.name}</div>;
};

🆕 What's New

Batch Update API

  • Batch mode can now be enabled via the second argument:

    const [state, proxy] = useImmerObservable(initialState, true);

    When batch mode is enabled from the beginning, state changes are not immediately applied. You must call proxy.update() after your changes to apply them. This is required for every update while batch mode is enabled.

  • Manual batch mode API (for advanced use): If you want to control batch mode yourself, you can enable and disable it as needed:

    proxy.enableBatch(true);
    proxy.set.user.name = "Carol";
    proxy.set.user.age = 42;
    proxy.update(); // Apply all changes at once
    proxy.enableBatch(false);

    In most cases, you do not need to call enableBatch if you started with batch mode enabled.

  • Scoped batch API:

    proxy.batch(() => {
      proxy.set.user.name = "Dave";
      proxy.set.user.age = 50;
    }); // All changes applied in a single render

    Note: When using proxy.batch:

    • Batch mode is automatically enabled only during the callback
    • After the callback finishes (even if an error occurs), batch mode is always restored to its previous state
    • Error Handling: The callback should be designed to not throw exceptions. If an uncaught exception occurs:
      • The React state will remain unchanged (changes are rolled back)
      • The proxy object may be left in a partially modified state
      • Always validate data before making updates to prevent exceptions

🔄 Comparison with use-immer

Both use-immer and use-immer-observable enable immutable updates using Immer, but they differ slightly in how you write updates.

Note: use-immer-observable allows you to write more intuitive, mutable-style code, but uses JavaScript Proxy internally, which introduces some runtime overhead compared to use-immer.

use-immer

You update state using a function passed to update():

onClick={() => {
  update(draft => {
    draft.user.isLoggedIn = true;
  });
}}

use-immer-observable

You can directly mutate the proxy like regular JavaScript objects:

onClick={() => {
  proxy.set.user.isLoggedIn = true;
}}

🌐 Global State with React Context

You can manage your state and proxy globally using React Context:

import React, { createContext, useContext } from "react";
import useImmerObservable from "use-immer-observable";

// 1. Create context
const GlobalStateContext = createContext(null);

// 2. Provider component
export const GlobalStateProvider = ({ children }) => {
  const [state, proxy] = useImmerObservable({
    user: { name: "Alice", isLoggedIn: false },
  });
  return (
    <GlobalStateContext.Provider value={{ state, proxy }}>
      {children}
    </GlobalStateContext.Provider>
  );
};

// 3. Custom hook for easy access
export const useGlobalState = () => useContext(GlobalStateContext);

// 4. Usage in components
function LoginButton() {
  const { proxy } = useGlobalState();
  return (
    <button
      onClick={() => {
        proxy.set.user.isLoggedIn = true;
      }}
    >
      Log In
    </button>
  );
}

function UserInfo() {
  const { state } = useGlobalState();
  return (
    <div>
      User: {state.user.name} ({state.user.isLoggedIn ? "Logged In" : "Guest"})
    </div>
  );
}

This pattern allows you to share and mutate state from anywhere in your component tree, just like with other global state solutions.


⚠️ Important Caveats

Mutating arrays directly won't work

proxy.set.items.push(4); // ❌ No re-render will occur

This does not trigger state updates because .push() mutates the array in-place and doesn't trigger the Proxy's set trap.

✅ To update arrays correctly, assign a new array:

proxy.set.items = [...proxy.set.items, 4]; // ✅ triggers re-render

structuredClone Limitation

This library uses structuredClone to deeply clone the initial state and reset state when needed.

Only objects supported by structuredClone are safe to use. Avoid:

  • Functions
  • Class instances
  • DOM nodes
  • Circular references

Peer Dependencies

  • React 18 or 19 is required
  • A modern browser with structuredClone support

License

MIT License © 2025 syogandev


Repository

https://github.com/syogandev/use-immer-observable