Skip to content

Introducing rmemo

Brian Takita
Authors:Brian Takita
Posted on:December 4, 2023

Introducing the rmemo reactive state management library.

I'm pleased to announce rmemo, a tiny no-fluff reactive state library. rmemo is the reactive core of relementjs, a server & browser UI rendering library that I also released.

What is rmemo?

rmemo is a tiny no-fluff state management library. It offers reactive memos & reactive signals for the server & browser. This includes:

  • reactive memos
  • reactive signals
  • autosubscriptions
  • async support
  • a terse & focused api
  • performance
  • integration with garbage collector
imports size
memo_ 338 B
memo_ + sig_ 354 B
memo_ + sig_ + be_ + ctx_ 506 B
memo_ + sig_ + be_ + ctx_ + be_memo_pair_ + be_sig_triple_ 602 B

It looks something like:

// users.ts
import { sig_ } from 'rmemo'
export const user_a$ = sig_<User[]>([], user_a$=>
    fetch('https://an.api/users')
        .then(res=>res.json())
        .then(user_a=>user_a$.set(user_a)))
export function user__add(user:User) {
    user_a$([...user_a$(), user])
}
export interface User {
    id:number
    name:string
}

Garbage Collection Integration

rmemo tracks listeners using WeakRef. Most other reactive state management libraries track listener references. This means the listener must unregister itself once the listener is complete. Let's examine the trade-offs:

Motivation

Until recently I was using Nano Stores for my goto state management solution. I contributed to fixing diamond dependency issues. I also wrote some extension libraries[1]. Nanostores is small, usable on the server & browser, & works with any UI rendering library. All seemed well & I used Nanostores for some large projects. The api is cumbersome in a few ways:

computed requires it's parents to be listed as arguments.

import { atom, computed } from 'nanostores'
const user_cache = {
  1: { id: 1, name: `Joe Blo` }
}
const id$ = atom(1)
const user$ = computed(id$, id=>user_cache[id])

async operations must use a writable atom & subscribe[2]

import { atom } from 'nanostores'
const id$ = atom(1)
const user$ = atom()
id$.subscribe(async id=>{
  const user = id ? await user__get(id) : null
  user$.set(user)
})
function user__get(id:number) {
  return fetch('https://my.api/users/' + id)
    .then(res=>res.json())
}

Should be smaller

atom atom + computed atom + computed + contexts (next release)
298 B 1013 B > 1200 B

The bundle size of nanostores is tiny when only using atom but grows over 1 kb when including computed.

[1]:

@ctx-core/nanostores & @ctx-core/solid-nanostores

[2]:

The next major version will integrate task with computed.