Custom Helper

To create a custom helper, all you need to do is create a function. If you expect to receive a state value you can wrap it in the helper function and handle the state value. All that this library can help you with.

Let's look at this simple render of a todo list.

const [todos, updateTodos] = state([
  {name: "buy groceries", status: "pending"},
  {name: "go to gym", status: "done"},
  {name: "prepare for xmas", status: "pending"},
  {name: "start a yt channel", status: "pending"},
])

html`${repeat(todos, t => html`<p>${t.name}</p>`)}`

Let's build a helper that can filter the todos based on status.

The simplest way is to just create a function that takes a list and function that returns a boolean and gets called for each item.

const filterList = (list, filterer) => {
  return list.filter(filterer)
}

We can use such a filter function as so:

html`${repeat(
  filterList(
    todos(), 
    t => t.status === "done"
  ), 
  t => html`<p>${t.name}</p>`
)}`

This works perfectly for the first render. It fails when the todo list state changes. This is because the template only performs a DOM update when it sees the state.

The first thing we can do is wrap the function with the helper function so the template knows to look for a state.

const filterList = helper((list, filterer) => {
  return list.filter(filterer)
})

But we are not passing the todos state to the helper, we are just calling it to pass the value to the filterList.

html`${repeat(
  filterList(
    todos(), // <- here 
    t => t.status === "done"
  ), 
  t => html`<p>${t.name}</p>`
)}`

Instead, we can call it inside the helper instead.

const filterList = helper((list, filterer) => {
  return list().filter(filterer)
})

All the issues are now fixed and the final result looks like so.

const filterList = helper((list, filterer) => {
  return list().filter(filterer)
});

html`${repeat(
  filterList(
    todos,
    t => t.status === "done"
  ), 
  t => html`<p>${t.name}</p>`
)}`

However, the filterList only works for state values. Ideally, you want a helper to work if a state, raw, or helper value is provided. To do that, we can use the val utility.

const filterList = helper((list, filterer) => {
  return val(list).filter(filterer)
})

Now the helper is complete and would work for any type of data. Here is the full example in typescript.

import {
  html,
  state,
  helper,
  val,
  repeat,
  StateGetter
} from "@beforesemicolon/markup";

const filterList = helper(<T>(
  list: T[] | StateGetter<T[]>,
  filterer: (item: T) => boolean
) => {
  return val<T[]>(list).filter(filterer)
});

interface ToDo {
  name: string;
  status: "done" | "pending";
}

const [todos, updateTodos] = state<ToDo[]>([
  {name: "buy groceries", status: "pending"},
  {name: "go to gym", status: "done"},
  {name: "prepare for xmas", status: "pending"},
  {name: "start a yt channel", status: "pending"},
])

html`${repeat(
  filterList(
    todos,
    t => t.status === "done"
  ), 
  t => html`<p>${t.name}</p>`
)}`.render(document.body);