Skip to content

Instantly share code, notes, and snippets.

@nijynot
Last active July 11, 2025 04:14
Show Gist options
  • Save nijynot/1b44438d8fe09eca51be28b3aaa55961 to your computer and use it in GitHub Desktop.
Save nijynot/1b44438d8fe09eca51be28b3aaa55961 to your computer and use it in GitHub Desktop.

Choo patterns

Table of Contents

  1. Closure
  2. Mutate closure variables
  3. Callback
  4. Passing emit and binding

1. Closure

The count variable is fully local, and does not cause "global state bloat". The local state is preserved per session, so would vanish on refresh, but this is perfect for loading, isDropdownOpen, and these kind of variables.

Important to call emit('render') in such a "closure component".

function Counter() {
  let count = 0

  return (state, emit) => {
    const handleClick = () => {
      count++
      emit('render')
    }

    return html`
      <div>
        <div>${count}</div>

        <button onclick=${handleClick}>
          Click me
        </button>
      </div>
    `
  }
}

// Rendering the view
${state.cache(Counter, 'counter')(state, emit)}

2. Mutate closure variables

Closure variables can be harder to access as they are not in the state variable. But, because they are cached by the state.cache function that is defined as

function renderComponent (Component, id) {
  assert.equal(typeof Component, 'function', 'choo.state.cache: Component should be type function')
  var args = []
  for (var i = 0, len = arguments.length; i < len; i++) {
    args.push(arguments[i])
  }
  return cache.render.apply(cache, args)
}

the values that are assigned to the function before you return the view, are cached inside Choo's cache as well. So then, we can mutate the closure variables as following:

function MyToggleComponent() {
  let isOpen = false

  const view = (state, emit) => {
    return html`
      <div>
        ${isOpen
          ? html`<div>i am open!</div>`
          : html`<div>i am closed.</div>`}
      </div>
    `
  }

  view.toggle = () => {
    isOpen = !isOpen
  }

  return view
}

// Trigger somewhere else, the `toggle` function
state.cache(MyToggleComponent, 'MyToggleComponent1').toggle()

3. Callbacks

Callbacks are a good pattern to execute some code after an emitter event has finished.

// In the component
const handleClick = () => {
  emit('some-event', () => {
    console.log('this will call after the event finished!')
  })
}

// In the store
emitter.on('some-event', (cb: (success: boolean) => void) => {
  let success = false

  try {
    // Do something
    // ...

    success = true
  } catch {
    // On error
    // ...
  } finally {
    cb(success)
  }
})

4. Passing emit and binding

If you have a function that takes state, emit as parameters, and if you have the emitter then you need to pass the emit as emitter.emit.bind(emitter).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment