← Back to all hooks

useImmer

Immutable state with drafts. Write "mutating" code that produces immutable updates, inspired by Immer.

Live Demo

A todo list demonstrating how useImmer simplifies complex nested state updates.

✅ Todo List with Immer

Learn useImmer
#learning
Build something cool
#project

📝 Event Log

No events yet

💡 How It Works:

  • Write "mutating" code that produces immutable updates
  • No need for spread operators or complex cloning
  • Simpler, more readable state updates
  • Works with nested objects and arrays
  • Inspired by Immer library

Code Examples

The Problem: Complex Immutable Updates

// ❌ Without Immer - verbose and error-prone
const [state, setState] = useState({ todos: [] });

const toggleTodo = (id) => {
  setState(prev => ({
    ...prev,
    todos: prev.todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    )
  }));
};

const addTag = (todoId, tag) => {
  setState(prev => ({
    ...prev,
    todos: prev.todos.map(todo =>
      todo.id === todoId
        ? { ...todo, tags: [...todo.tags, tag] }
        : todo
    )
  }));
};

The Solution: useImmer

import { useImmer } from "@uiblock/hooks";

// ✅ With Immer - simple and intuitive
const [state, updateState] = useImmer({ todos: [] });

const toggleTodo = (id) => {
  updateState(draft => {
    const todo = draft.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  });
};

const addTag = (todoId, tag) => {
  updateState(draft => {
    const todo = draft.todos.find(t => t.id === todoId);
    if (todo) {
      todo.tags.push(tag);
    }
  });
};

Nested Object Updates

import { useImmer } from "@uiblock/hooks";

function UserProfile() {
  const [user, updateUser] = useImmer({
    name: 'John',
    address: {
      street: '123 Main St',
      city: 'New York',
      country: 'USA'
    },
    preferences: {
      theme: 'dark',
      notifications: true
    }
  });

  const updateCity = (newCity) => {
    updateUser(draft => {
      draft.address.city = newCity;
    });
  };

  const toggleNotifications = () => {
    updateUser(draft => {
      draft.preferences.notifications = !draft.preferences.notifications;
    });
  };

  return <div>{/* UI */}</div>;
}

Array Operations

import { useImmer } from "@uiblock/hooks";

function ShoppingCart() {
  const [cart, updateCart] = useImmer({ items: [] });

  const addItem = (product) => {
    updateCart(draft => {
      draft.items.push({
        id: product.id,
        name: product.name,
        quantity: 1
      });
    });
  };

  const removeItem = (id) => {
    updateCart(draft => {
      const index = draft.items.findIndex(item => item.id === id);
      if (index !== -1) {
        draft.items.splice(index, 1);
      }
    });
  };

  const updateQuantity = (id, quantity) => {
    updateCart(draft => {
      const item = draft.items.find(item => item.id === id);
      if (item) {
        item.quantity = quantity;
      }
    });
  };

  return <div>{/* UI */}</div>;
}

useImmerReducer

import { useImmerReducer } from "@uiblock/hooks";

function reducer(draft, action) {
  switch (action.type) {
    case 'ADD_TODO':
      draft.todos.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
      break;
    case 'TOGGLE_TODO':
      const todo = draft.todos.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
      break;
    case 'DELETE_TODO':
      const index = draft.todos.findIndex(t => t.id === action.payload);
      if (index !== -1) {
        draft.todos.splice(index, 1);
      }
      break;
  }
}

function TodoApp() {
  const [state, dispatch] = useImmerReducer(reducer, { todos: [] });

  return (
    <div>
      <button onClick={() => dispatch({ type: 'ADD_TODO', payload: 'New task' })}>
        Add Todo
      </button>
      {/* ... */}
    </div>
  );
}

API Reference

useImmer(initialState)

initialState: S | (() => S)

Initial state or lazy initializer

Returns: [state, updateState] - Current state and updater function

useImmerReducer(reducer, initialState)

reducer: (draft: S, action: A) => void | S

Reducer function that mutates draft

initialState: S

Initial state

Returns: [state, dispatch] - Current state and dispatch function

💡 Use Cases

  • Complex nested state updates
  • Todo lists and task managers
  • Form state management
  • Shopping carts
  • Tree structures and hierarchical data
  • Any state with deep nesting