DEV Community

Cover image for Seamless State Management using Async Iterators
Matt
Matt

Posted on

Seamless State Management using Async Iterators

In my recent post about AI-UI, I touched on why I developed the library.

  • I wanted declarative markup
  • I wanted type-safe component encapsulation, with composition & inheritance
  • I wanted a small, fast, client-side solution with no complex build process (or any build process!)

...but most importantly...

  • I didn't want to learn a whole new API just to manipulate data I already have in my app

In my single page apps, I've got remote APIs and endpoints that supply data. Sure, I might want to do a bit of arithmetic, sorting, formatting, but basically, I want to say to the UI: "Here's my data, render it into the DOM". When the data changes, either because the server tells me or the user manipulates it locally, I don't want to re-render anything - I've already laid out my page - I just want the DOM to automagically re-render.

How can I do this using the most familiar JS syntax possible? Iterable Properties

Show me!

Let's start with one of the basic examples in the AI-UI repo: a clock.

We're not really interested in the clock itself, we're going to use Chrome Dev tools create iterable properties on a basic JS object, and see how they work. Go to the example, and open Dev Tools with Ctrl (Cmd) + Shift + I

Create an object

We're using this script (as opposed to the ECMA module) as it defines the global constant AIUI, so we can just play.

A key sub-module of AIUI is the Iterators interface. Let's grab a reference to it for later use, and just create a plain old JS object.

In the Dev Tools console, type:

defineIterableProperty = AIUI.Iterators.defineIterableProperty;
xxx = { foo: 123 };
Enter fullscreen mode Exit fullscreen mode

So xxx is just an object. Nothing special here. Let's add an iterable property to it. The syntax is defineIterableProperty(object, key, initialValue):

defineIterableProperty(xxx,"bar",456);
// xxx.bar just looks like a normal property:
xxx.bar * 10
// 4560
xxx.bar -= 111;
// 345
Enter fullscreen mode Exit fullscreen mode

Add an iterable property and use as normal

But xxx.bar has some magic: it's also an async iterator:

for await(const v of xxx.bar) console.log("bar is now",v)
// bar is now 345
xxx.bar = 100
// bar is now 100
//100
xxx.bar++
// bar is now 101
// 100
xxx.bar = "Wow!"
// bar is now Wow!
//'Wow!'
Enter fullscreen mode Exit fullscreen mode

If you prefer a more functional style, AI-UI comes with a handy bunch of helpers like map, filter and consume

xxx.bar.map(v => v * 10).consume(v => console.log("Or 10x more is",v));
// Promise {<pending>}  
xxx.bar = 99;
// bar is now 99          <--- for...await is still going!
// Or 10x more is 990     <--- same value mapped
// 99
Enter fullscreen mode Exit fullscreen mode

Watch it change

Is that it?

Yep. That's it. No 'setState()', no hooks, signals, or other API to learn. You just define an iterable property, read and write to it like normal, and consume the results as it changes.

It's this simplicity that makes AI-UI one of the simplest UI modules to use. Just layout your markup specifying iterable values in your variable substitutions like <div>${xxx.bar}</div>, and it just works by consuming your iterable values and updating the DOM.

Of course, defining all your own properties by hand is a bit repetitive, so AI-UI components can create a bunch for you using the iterable member, but that's not the core functionality.

The Iterators submodule is pure-JS, with no DOM dependencies, so you can use it in NodeJS apps with

import '@matatbread/ai-ui/esm/iterators.js';
Enter fullscreen mode Exit fullscreen mode

Start iterating now!

Top comments (0)