forimmer is a experimental functional state store for react apps, which uses immer for immutability and integrates with the new react suspense API. It was in part inspired by the excellent pullstate.
The name is just a silly play with words. "immer" means "always" or "ever" in German and "für immer" means "forever". So, forimmer is basically just a fun, bad translation (that you might encounter from actual Germans trying to speak English). I'm not good at naming things.
First, here is a codesandbox with a very contrived example: link
You can install forimmer from npm:
npm install forimmer -s
A new store is created with the createStore function:
import { createStore } from "forimmer";
const store = createStore<{
foo: string;
bar: string;
}>();
You can also supply an initial state:
import { createStore } from "forimmer";
const store = createStore<{
foo: string;
bar: string;
}>({foo: "foo"});
If your initial state covers the entire desired State interface, you may omit the generics and createStore will infer the type of your state:
import { createStore } from "forimmer";
const store = createStore({foo: "foo", bar: "bar"});
If you just want to access the current state of the store, use:
const state = store.getCurrentState();
The useStoreState hooks allows you to pick values from the store (returning them in an array) and subscribe to changes thereof:
function SomeComponent() {
const [foo] = useStoreState(store, state => [state.foo]); // useStoreState will infer the type of foo
return <div>{foo}</div>
}
Whenever state.foo changes (and not any other value), the component will re-render and foo will be updated to the new value.
If any of the values returned from useStoreState is undefined OR if trying to access a value deeper in the state tree would throw an error, useStoreState will throw a Promise which resolves once the store has been updated and the desired values become available.
You can wrap your component in React.Suspense to show a fallback UI that will automatically appear when useStoreState throws and the loading Promise is pending:
function ComponentWithSuspense() {
return <React.Suspense fallback={<div>...loading foo</div>}>
<SomeComponent />
</React.Suspense>
}
In order to modify the store state, you can define store actions. A store action is an async function (that might for instance fetch data from an API) which returns a state recipe, which in turn is a function which is passed a draft state to modify (see the immer docs):
// this will create a store action with a payload:
const setFoo = store.createStoreAction(
async (newFoo: string) => draft => {
draft.foo = newFoo;
}
);
setFoo("foo") // set store.foo to "foo"
const fetchFoo = store.createStoreAction(
async () => {
const foo:string = await someApiCall();
return draft => {
draft.foo = newFoo;
}
}
);
fetchFoo() // fetch foo from some api