use-immer-state-provider
v1.0.3
Published
React hook and provider for immutable state with actions api
Downloads
48
Readme
use-immer-state-provider
A React state provider and a hook factory to read and manipulate an immutable state using immer and a ✨ magical actions api.
Installation
npm install immer use-immer use-immer-state-provider
API
createImmerStateContext
createImmerStateContext(initialState: T, actions: ActionsT)
is similar to createContext
.
The function returns a Context (same as React's createContext
will) and
initialValue
which will be the initial value of that context and will also expose the types of its elements to the consumer.
defaultValue
should have the type of the state we would like to have.actions
should be an object of functions, where the functions have a signature of(draft: T, ...args)
Extracting the API's type
export type MyStateApi = (typeof result.initialValue)[1];
useImmerStateProvider
useImmerStateProvider(initialState: T, actions: ActionsT, onError?: (e: Error) => any)
This hook function is used inside the provider component.
- Returns
[state, api, value]
state
- Current state (type ofinitialState
)api
- An API to update the state (map of functions, static reference; doesn't change), using names fromactions
and parameters without the firstdraft
parametervalue
- To be used inside as thecontext.Provider
'svalue
prop.
Example
import { PropsWithChildren, useContext } from "react";
import { createImmerStateContext, useImmerStateProvider } from "use-immer-state-provider";
export type User = { id: string, name: string, email: string };
// Define the state type
type UsersState = {
users: User[];
};
const initialState: UsersState = {
users: []
};
const actions = {
addUser: (draft: UsersState, user: User) => {
draft.users.push(user);
},
deleteUser: (draft: UserState, id: string) => {
const index = draft.users.findIndex(u => u.id === id);
if (index > -1) {
draft.users.splice(i, 1);
}
},
setName: (draft: UserState, id: string, name: string) => {
const index = draft.users.findIndex(u => u.id === id);
if (index > -1) {
draft.users[i].name = name;
}
},
setEmail: (draft: UserState, id: string, email: string) => {
const index = draft.users.findIndex(u => u.id === id);
if (index > -1) {
draft.users[i].email = email;
}
},
};
const { context, initialValue } = createImmerStateContext(initialState, actions);
export type UsersApi = (typeof initialValue)[1];
export const useUsers = () => {
return useContext(context);
};
export const UsersProvider = ({ children }: PropsWithChildren<{}>) => {
const [, api, value] = useImmerStateProvider(initialState, actions);
// if you want, you can extract state too and do something with it here
return <context.Provider value={value}>{children}</context.Provider>;
};
And inside a component:
import { useUsers } from './usersProvider';
const UserList = () => {
const [{ users }, api] = useUsers();
return (
<div>
<ul>
{users.map((user, i) => (
<li key={user.id}>
{user.name}
<button onClick={() => api.deleteUser(user.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
- Notice:
api.deleteUser(user.id)
we use the same function name we created in theactions
object but we didn't use the draft argument. This is done by some Typescript magic ✨. - Another cool thing you get out of it is that the IDE can detect where a function of the API is being used by searching usages.