Gathering detailed insights and metrics for @shrugsy/use-immer-state
Gathering detailed insights and metrics for @shrugsy/use-immer-state
Gathering detailed insights and metrics for @shrugsy/use-immer-state
Gathering detailed insights and metrics for @shrugsy/use-immer-state
A React hook that provides a supercharged version of the `useState` hook. Allows for writing easy immutable updates. Provides additional functionality such as time travel state.
npm install @shrugsy/use-immer-state
Typescript
Module System
Node Version
NPM Version
72
Supply Chain
98.9
Quality
76
Maintenance
100
Vulnerability
100
License
TypeScript (91.85%)
JavaScript (8.15%)
Total Downloads
17,900
Last Day
2
Last Week
17
Last Month
102
Last Year
1,851
MIT License
5 Stars
51 Commits
1 Forks
1 Watchers
1 Branches
1 Contributors
Updated on Mar 09, 2023
Minified
Minified + Gzipped
Latest Version
3.1.2
Package Id
@shrugsy/use-immer-state@3.1.2
Unpacked Size
127.60 kB
Size
25.48 kB
File Count
16
NPM Version
6.14.12
Node Version
12.22.1
Cumulative downloads
Total Downloads
Last Day
-50%
2
Compared to previous day
Last Week
-73%
17
Compared to previous week
Last Month
39.7%
102
Compared to previous month
Last Year
-56.7%
1,851
Compared to previous year
1
1
20
A React hook that provides a supercharged version of the useState
hook. Allows for writing easy immutable updates. Heavily inspired by @reduxjs/redux-toolkit's usage of immer
.
npm i @shrugsy/use-immer-state
Within your React app:
1import { useImmerState } from "@shrugsy/use-immer-state";
useState
.immer
to produce the next immutable state.Note: If you're looking to be able to write 'mutable' draft updates for more complex state, I recommend either:
- Check out use-local-slice.
- Use
createReducer
from@reduxjs/toolkit
in combination with the inbuiltuseReducer
hook.
At it's core, it can be used identically to the inbuilt useState
hook.
e.g.
1import { useImmerState } from "@shrugsy/use-immer-state"; 2 3const [user, setUser] = useImmerState({ id: 1, name: "john smith" }); 4 5function handleUpdateUser(newName) { 6 // nothing special here, this is how you might do it with `useState` currently 7 setState({ ...user, name: "Jane Doe" }); 8}
When using a callback to perform functional updates, behaviour is as follows:
useState
)mutably
, but internally produce the next immutable update, without mutating the statee.g.
1const [user, setUser] = useImmerState({ id: 1, name: "john smith" });
2
3function handleUpdateUser(newName) {
4 // the functional update notation allows writing the update mutably, and will internally produce an immutable update without mutating the actual state
5 setState((prev) => {
6 prev.name = "Jane Doe";
7 });
8}
The benefits shine more for nested updates that would be messy to write manually. e.g.
1// given some initial state like so: 2const initialState = [ 3 { 4 todo: "Learn typescript", 5 done: true, 6 }, 7 { 8 todo: "Try use-immer-state", 9 done: false, 10 }, 11 { 12 todo: "Pat myself on the back", 13 done: false, 14 }, 15];
1const [todos, setTodos] = useImmerState(initialState); 2 3function handleToggleTodo(index, isDone) { 4 setTodos((prevTodos) => { 5 if (prevTodos[index]) prevTodos[index].done = isDone; 6 });
Note: To achieve a similar effect with plain useState
,
the update would look more like this:
1const [todos, setTodos] = useState(initialState); 2 3function handleToggleTodo(index, isDone) { 4 setTodos((prevTodos) => { 5 return prevTodos.map((todo, idx) => { 6 if (idx !== index) return todo; 7 return { ...todo, done: isDone }; 8 }); 9 }); 10}
Note that the deeper the nested updates become, the larger the advantage will be to use this notation.
The tuple returned by useImmerState
includes an optional third value; extraAPI
like so:
1const [state, setState, extraAPI] = useImmerState(initialState);
Note that you can name the value anything you like, or de-structure the values out directly.
state
is the current state, similar to what you would receive from useState
.
setState
is the function to use when updating state, similar to what you would receive from useState
.
Key differences:
mutably
, and are applied immutable
behind the scenesextraAPI
is an object that contains the following values:
Note: For the purposes of the table below,
S
refers to the type ofinitialState
.
Name | Type | Description |
---|---|---|
history | ReadOnlyArray<S> | (default [initialState]) An array of the state history |
stepNum | number | (default 0) The current step (index) within the state history |
isFirstStep | boolean | Whether the current step is the first step (i.e. if stepNum === 0) |
isLastStep | boolean | Whether the current step is the last step (i.e. if stepNum === history.length - 1) |
goTo | (step: number) => void | Change the current state to a particular step (index) within the state history |
goBack | () => void | Go to the previous step (index) within the state history |
goForward | () => void | Go to the next step (index) within the state history |
saveCheckpoint | () => void | Saves the current step (index) within the state history to a 'checkpoint' that can be restored later |
restoreCheckpoint | () => void | Restores the state to the saved 'checkpoint' if it is still valid |
checkpoint | number | (default 0) The step (index) within the state history for the saved checkpoint |
isCheckpointValid | boolean | (default true) Indicates whether the saved checkpoint is valid and accessible to restore. A checkpoint will be invalidated if the history gets overwritten such that it overwrites the saved checkpoint. History is overwritten when writing new state while at a step number besides the latest. |
reset | () => void | Resets state, history and checkpoint back to the initial state. |
Please try the codesandbox demo to see an example of the API in action.
This library expects that mutating logic is only written using the functional update notation within a setState
call. Any attempts to mutate the state outside of this are not supported.
If an uncontrolled mutation is detected, a MutationError
will be thrown (a custom error type exported by this library), and the path detected will be logged to the console to highlight the detected mutation and assist with detecting the cause.
See this codesandbox example to view how the mutation is detected and shown in the console.
Note:
This feature is disabled in production mode.
By default, immer freezes the state recursively after it has been used. This means that attempted mutations will not have an effect, but will not reliably be detected and throw an error for every setup/browser when the attempt is made.
What this means is that the mutation may only be detected in between the first and second state.
This library re-exportssetAutoFreeze
fromimmer
which can help narrow down invalid mutation attempts, as callingsetAutoFreeze(false)
will prevent immer freezing the state, and allow the mutation detection from this library to reliably detect uncontrolled mutations occurring to a serializable state value.
The following items are re-exported from other libraries for ease of use:
setAutoFreeze - Enables / disables automatic freezing of the trees produces. By default enabled.
current - Given a draft object (doesn't have to be a tree root), takes a snapshot of the current state of the draft
original - Given a draft object (doesn't have to be a tree root), returns the original object at the same path in the original state tree, if present
castDraft - Converts any immutable type to its mutable counterpart. This is just a cast and doesn't actually do anything
Draft - Exposed TypeScript type to convert an immutable type to a mutable type
See the following links for more information on the immer API: https://immerjs.github.io/immer/api/
The following type definitions are used by this library internally and are exported for typescript users to use as required.
1/** Initial state provided to the hook */ 2export declare type InitialState<S> = S | (() => S);
1/** New state, or a state updater callback provided to a `setState` call */ 2export declare type Updates<S> = S | ((draftState: Draft<S>) => Draft<S> | void | undefined);
1/** Function used to update the state */ 2export declare type SetState<S> = (updates: Updates<S>, includeInHistory?: boolean) => void;
1/** Extra API used for time travel features */ 2export declare type ExtraAPI<S> = { 3 history: readonly S[]; 4 stepNum: number; 5 isFirstStep: boolean; 6 isLastStep: boolean; 7 goTo: (step: number) => void; 8 goBack: () => void; 9 goForward: () => void; 10 saveCheckpoint: () => void; 11 restoreCheckpoint: () => void; 12 checkpoint: number; 13 isCheckpointValid: boolean; 14 reset: () => void; 15};
1/** Return value of the hook */ 2export declare type UseImmerStateReturn<S> = readonly [S, SetState<S>, ExtraAPI<S>];
1/** 2 * Hook similar to useState, but uses immer internally to ensure immutable updates. 3 * Allows using the setter function to be written 'mutably', 4 * while letting immer take care of applying the immutable updates. 5 * 6 * Provides time travel support including `history`, `checkpoints`, `goTo`, 7 * and `reset` functionality. 8 * 9 * If not in production mode, checks for mutations between renders and will 10 * throw an error if detected. 11 * 12 * https://github.com/Shrugsy/use-immer-state#readme 13 * @param initialState - initial state, or lazy function to return initial state 14 * @returns [state, setState, extraAPI]: 15 * - state - the current state 16 * - setState- A function to update the state 17 * - extraAPI - An object containing details and methods related to inbuilt time travel features 18 */ 19export declare function useImmerState<S = undefined>(): UseImmerStateReturn<S | undefined>; 20export declare function useImmerState<S>(initialState: InitialState<S>): UseImmerStateReturn<S>;
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no SAST tool detected
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
Reason
security policy file not detected
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
67 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-05-12
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More