Basic Guide

Lithent were developed to make it easy to insert Virtual DOM component fragments into pages already drawn with SSR, and are intended to be used lightly in a variety of situations.  (Detailed introduction)

After watching the nine simple examples below, you should have an almost complete understanding of Lithent.

Lesson1 - Mounter & Updater & Renew

Mounter

Called once when the component is first drawn. Defines the component's state and methods.

Updater

The definition of the template markup that is actually drawn. Called whenever the component's state is changed or updated.

An updater is a higher-order function defined within a mounter that uses JavaScript closures to access variables and functions in the mounter.

Renew

renew is used when you need to update a component from a method defined in the mounter.

import { h, Fragment, render, mount } from 'lithent'; // Mounter const Component = mount((renew, _props) => { let count = 0; const change = () => { count += 1; // Renew renew(); }; // Updater return () => ( <> <li>count: {count}</li> <button onClick={change}>increase</button> </> ); }); render(<Component />, document.getElementById('root'));
count: 0

Lesson 2 - Props

"props" is provided as the second argument to the "mounter" function and as the first argument to the "updater".

The updater function can access the "mounter"'s "props" value with a closure, but be careful because you can make the mistake of not getting the updated state if the value is "call by value".

import { h, Fragment, render, mount } from 'lithent'; const Children = mount<{ count: number }>((_r, props) => { const { count: countFromMounter } = props; return ({ count: countFromUpdater }) => ( <> <div>count: {props.count}</div> <div>count: {countFromMounter} ("call by value" not working)</div> <div>count: {countFromUpdater}</div> </> ); }); const Parent = mount(renew => { let count = 0; const change = () => { count += 1; renew(); }; return () => ( <> <Children count={count} /> <button onClick={change}>Increase</button> </> ); }); render(<Parent />, document.getElementById('root'));
count: 0
count: 0 ("call by value" not working)
count: 0

Lesson 3 - helper (state)

The "state" function defined in the example code delegates a value and a "renew" function from the component and then executes it whenever the value changes.

Users can implement and use several forms of helpers themselves by utilizing the "renew" function.

The state function used in the example is pre-implemented in 'lithent/helper', so you can just pull it out and use it to and use it. But it's just an example.

In addition to "state", we've implemented helper codes like "store", "computed", and "effect" in "lithent/helper", and their usage is described on the examples page.

import { h, Fragment, render, mount } from 'lithent'; // import { state } from 'lithent/helper'; // This is what the above commented out module looks like. const state = <T>(value: T, renew: () => boolean): { value: T } => { let result = value; return { get value() { return result; }, set value(newValue: T) { result = newValue; renew(); }, }; }; // Mounter const Component = mount((renew, _props) => { const count = state<number>(0, renew); const change = () => { count.value += 1; }; // Updater return () => ( <> <li>count: {count.value}</li> <button onClick={change}>increase</button> </> ); }); render(<Component />, document.getElementById('root'));
count: 0

Lesson 4 - render

render

The third argument to the "render" method allows you to insert a virtual DOM in front of the specified element.

The first argument to the "render" method is the virtual DOM, the second argument is the virtual DOM's parent element, and the third argument specifies the specific location where the virtual DOM will be inserted.

store helper

The "store" used in the examples is a helper implementation, like "state". More detailed usage can be found on the examples page.

// index.html /* <div> <span>1</span> <span>2</span> <span>3</span> </div> */ // app.tsx import { h, Fragment, render, mount } from 'lithent'; import { store } from 'lithent/helper'; const assignShardStore = store<{ text: string; count: number }>({ text: 'sharedText', count: 3 }); const Component = mount(r => { // The value of "shardStore.count" is null. // To get the value, you must include it in the second argument, the function return array. // If you omit the second argument, then all values in the store are fetched. const shardStore = assignShardStore(r, (store) => [store.text]); const changeInput = (event) => { shardStore.text = event.target.value; }; return () => <textarea type="text" onInput={changeInput} value={shardStore.text} />; }); render(<Component />, element, element.querySelector('span:nth-of-type(2)')); render(<Component />, element, element.querySelector('span:nth-of-type(3)'));
123

Lesson 5 - Root Destroy

The value returned by the render function is the destroy function. When executed, it is unmounted and removed from the DOM.

import { h, render, mount } from 'lithent'; /* <div> <button id="remove-button">Destroy Component</button> </div> */ const Component = mount(() => () => <strong>Component</strong>); const destroy = render(<Component />, element, element.querySelector('button')); document.getElementById('remove-button').addEventListener('click', destroy);
Component

Lesson 6 - mountCallback

The "mountcallback" runs after the component is created in the actual dom.

The function returned by "mountCallback" is executed on unmount.

import { h, Fragment, render, mount, mountCallback } from 'lithent'; const Children = mount( (_r, props) => { mountCallback(() => { console.log('mounted'); return () => { console.log('unmount'); }; }); return () => <span>Children</span>; } ); const Parent = mount(renew => { let count = 0; const change = () => { count += 1; renew(); }; return () => ( <> <button onClick={change}>Toggle</button> {count % 2 === 0 ? <Children count={count} /> : null} </> ); }); render(<Parent />, document.getElementById('root'));
mounted
Children

Lesson 7 - updateCallback

The "updateCallback" is executed after the component is requested to update, but before it is updated. It is used for clean up purposes.

The function returned by updateCallback is executed after the component update is complete.

Defines a function as the second argument that returns an array of target values when an update to a specific value needs to be detected. If omitted, it will always be executed.

By combining updateCallback and mountCallback, you can create a helper similar to react's useEffect. Check out the examples page to see how to use the effect helper.

import { h, Fragment, render, mount, updateCallback } from 'lithent'; const Children = mount<{ count: number }>((_r, props) => { updateCallback( () => { console.log('clean up'); return () => console.log('updated'); }, () => [props.count] ); return ({ count }) => <span>child updated count: {count}</span>; }); const Parent = mount(renew => { let count = 0; const change = () => { count += 1; renew(); }; return () => ( <> <button onClick={change}>Update</button> <Children count={count} /> </> ); }); render(<Parent />, document.getElementById('root'));
child updated count: 0

Lesson 8 - ref

A "ref" is simply an object with a "value" property. As shown in the example, a "ref" allows you to change the internal state of the root component from outside the root component.

Or Alternatively, you can use it as a way to access the actual DOM drawn by the virtual DOM, like the elementRef in the example.

import { h, mount, render, ref } from 'lithent'; import { state } from 'lithent/helper'; /* <div id="root"> <button>increase</button> </div> */ const Component = mount<{ apiRef: { value: null | (() => void) } }>( (r, { apiRef }) => { const count = state<number>(0, r); const elementRef = ref<null | HTMLElement>(null); apiRef.value = () => { count.v += 1; elementRef.value.style.border = '1px solid red'; }; return () => <strong ref={elementRef}>Component: {count.v}</strong>; } ); const apiRef = ref<null | (() => void)>(null); const element = document.getElementById('root'); element.querySelector('button').addEventListener('click', apiRef.value); render(<Component apiRef={apiRef} />, element);
Component: 0

Lesson 9 - portal

"portal" lets you render some children into a different part of the DOM.

In the example below, a child component defined after the parent component button will navigate to the portal before the parent component.

import { h, render, mount, portal } from 'lithent'; const Children = mount<{ count: number }>(() => { return ({ count }) => <span>child updated count: {count}</span>; }); const Parent = mount(renew => { let portalEl = ref(null); let count = 0; const change = () => { count += 1; renew(); }; // In our example, specifically, portalEl.value was not present the first time it was rendered, so re-render mountCallback(() => { renew(); }); return () => ( <> <div ref={portalEl} /> <button onClick={change}>Update</button> {portalEl.value && portal( <Children count={count} logEl={logEl} />, portalEl.value as HTMLElement)} </> ); }); render(<Parent />, document.getElementById('root'));
child updated count: 0