Using immer for immutable state in React

Using JavaScript’s spread operator is all well and god, but doing so when updating complex types in React can be an eyesore. That is, it just get unwieldy because I often have to spread objects properties which are themselves objects.

In the following example, UserProfile.Address is a nested object that we want to update when a button is clicked. You can see how I need to spread the nested object to get access to its members when calling the setUser state function.

import { useState } from 'react';

const UserProfile = () => {
    const [user, setUser] = useState({
        id: 1,
        name: 'John Doe',
        role: 'Administrator',
        email: 'johndoe@example.com',
        dateAdded: '2024-09-17',
        address: {
            street: '123 Main St',
        }
    })

    const updateUser = () => {
        const newStreetAddress = '456 Main St';
        setUser({...user, address: {...user.address, street: newStreetAddress}});
    }

    return (
        <>
            <h2>User Profile</h2>
            <p>ID: {user.id}</p>
            <p>Name: {user.name}</p>
            <p>Role: {user.role}</p>
            <p>Email: {user.email}</p>
            <p>Date Added: {user.dateAdded}</p>
            <p>Address: {user.address.street}</p>
            <button onClick={() => updateUser()}>Update User</button>
        </>
    )
}

export default UserProfile

The immure package can help us simplify this code to something a bit more intuitive. After installing the ‘immer’ package and importing its produce() function, we get a different experience.

import { useState } from 'react';
import { produce } from 'immer';

const UserProfile = () => {
    const [user, setUser] = useState({
        id: 1,
        name: 'John Doe',
        role: 'Administrator',
        email: 'johndoe@example.com',
        dateAdded: '2024-09-17',
        address: {
            street: '123 Main St',
        }
    })

    const updateUser = () => {
        
        const updatedUser = produce(user, draftUser => {
            draftUser.address.street = '456 Main St';
        });

        setUser(updatedUser);
    }

    return (
        <>
            <h2>User Profile</h2>
            <p>ID: {user.id}</p>
            <p>Name: {user.name}</p>
            <p>Role: {user.role}</p>
            <p>Email: {user.email}</p>
            <p>Date Added: {user.dateAdded}</p>
            <p>Address: {user.address.street}</p>
            <button onClick={() => updateUser()}>Update User</button>
        </>
    )
}

export default UserProfile

immer is designed to work exclusively with immutable state and provides a nice clean way to work with nested object structures. Even flat object updates benefit from the ability to

Proudly powered by WordPress | Theme: Code Blog by Crimson Themes.