What is a Helper?
A Markup helper is simply a function, which the main purpose is to help with some render logic or value in the template.
It can be used to check data, wrap or render templates,
manipulate data, etc. You can create simple functions and use
the template
update
method to trigger them all or wrap functions
in helper
call to make it work with template
states.
Simple helper
Bellow is a simple example of a helper. For this case this code
is using the template update
method to trigger
updates on the DOM. In this case, any function in the template
will get called with every update.
let count = 0;
// simple count helper function to display "even" or "odd"
const evenOddCountLabel = () => count % 2 === 0 ? 'even' : 'odd';
const counter = html`
<p>${() => count}</p>
<p>${evenOddCountLabel} count</p>
<button type="button" onclick="${countUp}">+</button>
`;
function countUp() {
count += 1;
counter.update()
}
counter.render(document.body)
As you can see, a helper is simply a function that does a particular calculation and returns something for a part of the template. This can be an attribute value or a template content.
For contrast, this is how it would look like if I was using state count instead.
let [count, setCount] = state(0);
const evenOddCountLabel = () => count() % 2 === 0 ? 'even' : 'odd';
const counter = html`
<p>${count}</p>
<p>${effect(count, evenOddCountLabel)} count</p>
<button type="button" onclick="${countUp}">+</button>
`;
function countUp() {
setCount(prev => prev + 1)
}
counter.render(document.body)
The difference is the usage of the effect
helper to
tell the template to execute the helper whenever the
count
changes since this particular helper uses the
count
state inside.
Helper wrapper
We can simplify the helper above with the
helper
wrapper and remove the need for the
effect helper when using it in the
template.
To use the helper
wrapper, all you need to do is
wrap a function with it.
const evenOddLabel = helper((x: StateGetter<number>) => x() % 2 === 0
? 'even'
: 'odd');
Notice that now it is a pure function that takes the
x
input which must be a StateGetter
.
What this mean is that, whenever the state changes, the helper
will tell the template to update that specific part of the DOM
where it is used.
Here is the full result:
let [count, setCount] = state(0);
const evenOddLabel = helper((x: StateGetter<number>) => x() % 2 === 0
? 'even'
: 'odd');
const counter = html`
<p>${count}</p>
<p>${evenOddLabel(count)} count</p>
<button type="button" onclick="${countUp}">+</button>
`;
function countUp() {
setCount(prev => prev + 1)
}
counter.render(document.body)
You can learn to improve it further by checking how to create your custom helpers.
Helper value and arguments
The helper
wrapper returns an instance of
Helper
that the template itself knows how to handle
by accessing the value
and
args
properties.
evenOddLabel(count).value;
evenOddLabel(count).args;
By knowing these two properties, you can use them anywhere in your code for any type of functionality you want to create.
Working with helpers
Helpers are just functions, and you can do anything you would with functions including returning or passing them around.
Below is a simple example of nesting helpers, in this case the
is
, when
, and
repeat
helpers.
html`${repeat(
when(is(userType, 'user'), ['name', 'status'], ['name', 'role']),
part => html`<strong>${part}</strong>`)
}`
What this is illustrating is the functional oriented nature of Markup, specifically the ability to compose simple functions to create complex renders and logic handlers.
Helper scope state
Helpers can also keep internal state through JavaScript closures, and it should not be a problem. Your helper function can be up to one level nested function.
The following filter use the outer function to "cache" the condition, and the filtered list results, so it can save unnecessary computation. The inner returned function is used as the handler.
const filterList = helper((list, filterer, condition = () => null) => {
let val = [];
let cond;
return () => {
if(condition() !== cond) {
val = list().filter(filterer);
cond = condition();
}
return val;
}
})
This is just a simple example, but it shows that you can nest functions up to one level (outer and inner) and the helper would still function well.
This capability will allow helpers to keep data between calculations, and you to build more powerful helpers to support your project.