How to use cross-island state management in Deno Fresh

(This is just a quick write-up since the question/ topic came up on the Deno Fresh discussion board (“State sharing between islands”) to which I wrote a short reply. I felt like elaborating here. The examples are stripped to bare minimum, so there’ll be no TS definitions and no import maps.)

Screenshot of the first piece of code from the text

Let’s say you’ve built a new site using the wonderful Deno Fresh web framework. You have a number of unconnected islands all over the place and now want them to interact with eachother without resorting to hacks like passing data around using some key on the window object. In my personal experience the best way to do that is by using some sort of state management — using a reactive store of arbitrary data.

There are a number of options here (Zustand, Redux, mobx etc. come to mind) but my preferred go-to is Statery by my guy @hmans.

Statery bills itself as “Surprise-Free State Management” and it’s 100% true. It does offer a store, the bare minimum of necessities to make that store and its state reactive, and then sends you on your merry way. Personally, I prefer that approach over the usual “does everything (and your mom)" — do one thing, and do it right. Statery nails that part.

So, how do I set up a store and make two (or more) components share state?

In a nutshell, a Statery store is just a JS module containing a variable which holds your state data. If you want persistance you’ll have to add it yourself. Also, you can have several separate stores doing different things.

Side note regarding persistance: For example, in my current app one store contains a simple subscriber storing the current state to the visitor’s local storage; when the visitor returns at a later date, during its initialization the store checks window.localStorage for existing store data and uses that to hydrate the store.

Let’s consider ./stores/whatever_store.ts:

import {
  makeStore,
  useStore,
} from "https://esm.sh/statery@0.5.4?alias=react:preact/compat&deps=preact@10.8.2";

/* Make a store */
const store = makeStore({ counter: 0 });

/* Write some code that updates it */
export const incrementCount = () =>
  store.set((state) => ({
    counter: state.counter + 1,
  }));

/**
 * A wee bit of abstraction so my components don't need to
 * import `useState` from Statery
 */
export const state = () => useStore(store);

Side note: notice the import URL here, telling ESM to substitute its React dependency with Preact 10.8.2 on the fly — which is what Fresh comes with.

The store is created only once the first time the module is loaded/ initialized, all the exports use it when called. Therefore all components using that particular store module access the same data, not different instances.

Next, let’s write two components, ./island/StoreUsingComponent1.tsx:

/** @jsx h */
import { h } from "preact";
import { incrementCount, state } from "../stores/whatever_store.ts";

export default function StoreUsingComponent1() {
  const { counter } = state();

  return (
    <p>
      <button onClick={incrementCount}>
        Increment number (clicked {counter} times)
      </button>
    </p>
  );
}

… and ./island/StoreUsingComponent2.tsx:

/** @jsx h */
import { h } from "preact";
import { state } from "../stores/whatever_store.ts";

export default function StoreUsingComponent2() {
  const { counter } = state();

  return <p>Current counter value: {counter}</p>;
}

Lastly, call them from a route or another island:

/** @jsx h */
import { h } from "preact";
import StoreUsingComponent1 from "../islands/StoreUsingComponent1.tsx";
import StoreUsingComponent2 from "../islands/StoreUsingComponent2.tsx";

export default function Demo() {
  return (
    <div>
      <StoreUsingComponent1 />
      <StoreUsingComponent2 />
    </div>
  );
}

And that’s all there is. Now when you click the button (located in StoreUsingComponent1.tsx) the text displayed by StoreUsingComponent2.tsx will change.

You could do the same thing w/ Zustand or any other state management library, of course. I just happen to prefer Statery’s sane and super-reduced API.

💬 Reply by email     ⭐️ Also on micro.blog
Carlo Zottmann @czottmann