useSticky

Simple and persistent state & fetch hooks built on Zustand and Immer.

Install

yarn add @ntds/sticky

Basics

Unlike the useApi-hook, which comes "pre-made," the useSticky-hook is actually created case-by-base in your application with the createSticky() function, like so:

const useMyState = createSticky<User>({
state: {
name: 'Guest',
},
});

Now, you can use your sticky hook, like any other hook in React:

function MyView() {
const state = useMyState();
return <h1>Hello, {state.name}</h1>;
}

Mutations

One of biggest drivers behind this hook is to minimize the boilerplate and make it fast & easy to prototype. As such, the hook combines mutations, selectors and potentially actions in the same definition and usage.

Out of the box, the state comes with a Immer-ready mutate-function, that used directly everywhere you use the hook:

function MyEditView() {
const { name, muate } = useMyState();
return (
<input
value={name}
onChange={({ target }) => {
mutate((draft) => {
draft.name = target.value;
});
}}
/>
);
}

In the example above, the mutate() is basically a wrapper around the produce() function in Immer.

Define mutations beforehand

Sometimes it can more practical to define the mutations alongside the hook creation. This is especially usfull in more complex mutations, where you want to include them in the tests for the hook.

Her are the same example as before, but with the setName() mutation predefined:

const useMyState = createSticky({
state: ({ mutate }) => ({
name: 'Guest',
setName(name) {
mutate((draft) => {
draft.name = name;
});
},
}),
});

Notis that the sate now is defined as a function that returns the state. This is to make available the mutate() function for the predefined mutator setName().

function MyEditView() {
const { name, setName } = useMyState();
return (
<input
value={name}
onChange={({ target }) => {
setName(target.value);
}}
/>
);
}

Persistence & Storage

If nothing else is defined, the persistens of the hook is basically the same as a Redux state. It will persevered in memory in the SPA as long as the page is not reloaded.

To persist the states between hard page loads, you can provide a storage & storageId. Typically, the storage is either sessionStorage or localStorage, but it can be any synchronous storage with the same API.

In the following example, we are using sessionStorage to persist the state:

const useMyState = createSticky<User>({
state: {
name: 'Guest',
},
storage: sessionStorage,
storageId: 'sticky-user-name',
// expireAfter: '24h', (optional)
});

Now, every time changes to the state are made, the storage will be updated.

You can also define expireAfter if you would like to discard the stored value at some point (this will only be checked when the page loads in).

Selectors

Since the sticky state is built on Zustand, you can also use a selector directly in the hook. This is useful if you have a larger state, and you don't want React to re-render the component for every change in the state.

function MyView({ couponIndex }) {
const numberOfCoupons = useSomeBigState((state) => state.coupons.length);
return <p>You have {numberOfCoupons} coupons</p>;
}

Now, React will only re-render the length of the coupon-list changes. Neat!

This guide is based on version: 1.1.0