thi-ng / umbrella

⛱ Broadly scoped ecosystem & mono-repository of 199 TypeScript projects (and ~180 examples) for general purpose, functional, data driven development
https://thi.ng
Apache License 2.0
3.41k stars 151 forks source link

[rdom] Error: illegal state: operation not allowed in state 3 #471

Closed bt-3000 closed 5 months ago

bt-3000 commented 6 months ago

Toggle Menu

import { $list, $switch } from '@thi.ng/rdom'
import { reactive, toggle, trace } from '@thi.ng/rstream'

const items = Array.from({ length: 4 }, (_, id) => ({ id, label: `Label ${id}` }))
const Item = x => ['li', { id: x.id }, x.label]
const items$ = reactive(items)

const isOpen$ = toggle(false)
isOpen$.subscribe(trace('isOpen'))
const onclick = () => isOpen$.next()

const Error = async e => ['error', {}, e]
const Loading = async () => ['div', {}, 'Loading...']

export const Menu = () =>
  [
    'div', {},
    ['button', { onclick }, 'Menu'],
    $switch(
      isOpen$,
      x => x ? 1 : 0,
      {
        0: s => '',
        1: s => $list(items$, 'menu', null, Item)
      },
      Error,
      Loading
    )
  ]

After 2 change state (call function) Error: illegal state: operation not allowed in state 3

noticed this error when $switch includes $list (ctrors)

postspectacular commented 5 months ago

Hi @bt-3000 - sorry for the slow response... The error (state:3) means that one of streams has already been closed (is complete) and attempting to write new values or subscribing to it will fail. This might sound counter-intuitive, but is down to the rstream default behavior of recursively garbage collecting stream/subscription topologies when a child subscription unsubscribes. In your example this happens to the items$ stream, when toggling the menu off again. When this happens, the $list() component will unsubscribe itself, which then propagates "upstream" to items$ which notices that its last/only subscriber is gone and therefore closes itself...

You can avoid this from happening by customizing the closing behavior of each stream, in this case like so:

const items$ = reactive(items, { closeOut: CloseMode.NEVER });

You can read more about it here: Common configuration options (or docs)

Also, another related tip for better debugging of rstream events & lifecycle: Set a package logger, like so:

import { ConsoleLogger } from "@thi.ng/logger";
import { LOGGER } from "@thi.ng/rstream";

LOGGER.set(new ConsoleLogger("rs", "DEBUG"));

When enabled, you'll see all sorts of output in the browser console. Also see the thi.ng/logger readme for more details/options...