Creating Components
Now that you have your page.ts(x) with a default export, it's time to add a component.
A component is not much different from an element, except that it can carry it's own state, and subscribe to lifecycle hooks like onMount.
From our previous example:
const counter = state(0); export default function CounterPage() { return div( button({ onClick: () => counter.value++, }, `Counter: ${counter.value}` ) ); }We can easily make button into a component like so:
const Counter = component({ atoms: { counter: 0, }, view(_, { counter }) { return button({ onClick: () => counter.value++, }, `Counter: ${counter.value}` ) } }) export default function CounterPage() { return Counter(); }You might wonder why we'd want to do this. In our original approach, if we ever wanted to have multiple instances of our Counter, you'd have to either create the element calls in a loop, or copy-paste the code. Each button would also still be referencing the same counter global state, so pressing one would increase the value of all other ones.
In other words, our extraction of the button into a component lets us have per instance state, and re-use the component more as a blueprint wherever we'd like.
Lifecycle Hooks
There are a few hooks you can subscribe to with components
For example, if we want our button to log the initial value of counter whenever it loads, we could use the onMount hook to do so:
const Counter = component({ atoms: { counter: 0, }, onMount(_, { counter }) { console.log(`The initial value of counter is: ${counter.value}`); }, view(_, { counter }) { return button({ onClick: () => counter.value++, }, `Counter: ${counter.value}` ) } }) export default function CounterPage() { return Counter(); }Self
You may have noticed that in the methods of our component like view and onMount, there has been a first parameter we have assigned as _ ( an underscore).
This value is the self of the component.
The self object contains a reference to the live DOM node that the component instance is linked to, as well as the props that this component was created with.
Props
Props are the only thing we haven't yet touched on.
They're values that the caller of a component can pass in, and are usually used to instantiate it.
For our earlier example, we can add an initialValue prop to start the counter at a desired value.
const Counter = component<{ initialValue: number, }, { counter: number, }>({ atoms: { counter: 0, }, onMount({ props }, { counter }) { counter.value = props.initialValue; }, view(_, { counter }) { return button({ onClick: () => counter.value++, }, `Counter: ${counter.value}` ) } }) export default function CounterPage() { return Counter({ initialValue: 10, }); }If we were to run this, we'd notice that the counter now starts at 10, instead of 0.
A more apt example of props would be something like a generic tooltip component or title, which takes in custom content.
const Title = Component<{ content: string, }>({ view({ props: { content } }) { return h1({ className: "title" }, content, ); } }) export default function page() { return Title("Welcome to My Page!"); }