Components
If you're not familiar with what a component is, you can familiarize yourself with them here.
View Method
The view method of a component defines the visual part of the component, and is responsible for returning a VirtualNode.
The view method must be synchronous, if you need async data within your component, use a lifecycle hook such as onMount to display a loading state.
Here's an example view:
const Component = component({ view() { return div("Hello, World!") } });If your view is large, you can extract it into a regular function, and wrap it with view() like so:
const ComponentView = view(() => { return div("Hello, World!"); }); const Component = component({ view: ComponentView, });This can be handy for large components, but is generally not what you'll be doing, as it makes type inference a nightmare.
Props
Props are values that the caller of a component can pass in to the component, and are usually used to instantiate it.
They can be of any kind of serializable value, just like atoms.
For static elements, the prop values are serialized and embedded into the client bundle.
To pass in props, first make the type of your component accept them like this:
const Component = component<{ myProp: string, }>({ view({ props: { myProp }}) { return div(myProp); }, });Once props are passed in, they'll be available via the props field of the self paramater that is passed into the methods of a component.
You can call a component with props like so:
Component({ myProp: "Hello, World!", });Lifecycle Hooks
onMount
The onMount hook gets called whenever a componentappears in the DOM after a render. It will not be called if the root DOM element of component.self existed during the previous render.
It will get called if the component stops existing in the dom, but is afterwards re-added.
onMount is an excellent callback for things like timers, event listeners, etc.
This callback does not get called for subsequent navigations where the same component exists within the new target page at the same DOM position. This is done because layout components must be the exact same DOM element and component instance across pages.
If you require a callback which runs on the component for each navigation regardless of whether it was remounted, you can use the onNavigate callback.
From onMount you should return a cleanup function like you do from onPageLoad that handles any side-effects.
onUnmount
The onUnmount hook gets called whenever a component instance is removed from the DOM during a render.
This is the inverse of onMount, and will also get called if the component is added and re-removed.
onNavigate
The onNavigate hook gets called after the user has navigated to another page.
onNavigate also gets called for the initial page load.
onNavigate will not be called if a component is removed from the DOM and then re-added, because that is not not a navigation.
From onNavigate you should return a cleanup function like you do from onPageLoad that handles any side-effects.
init
The init hook gets called during the first render of a component on the server. It's an async hook, meaning it's ideal for data fetching.
The init hook never ends up in the client-bundle. The actual function body is stripped from the resulting bundle, and any variable reference you make will thus be dead code and should get eliminated.
This hook is ideal whenever a component required server-side data you can get during the first-render, and it's never-client nature allows for greater DX.
Here is an example of how you can use init:
import { readFile } from "fs/promises"; const Component = component({ atoms: { content: "" as string, }, async init(_, { content }) { content.value = await readFile("./notes.txt") }, view(_, { content }) { return <div>{content.value}</div> } });Would result in a client-bundle akin to this:
// pages/test/page.tsx var Component = component({ __id: "eNYKSD7", atoms: { content: "" }, view(_, { content }) { return __tags.div([content.value]); } });And HTML akin to this:
<body> <template data-region="0"></template><div>Hello from notes.txt!</div> </body>You can see init and readFile are completely eliminated from the output bundle.
Important Notice
Elegance's DCE is still being actively developed, so it might not eliminate everything perfectly. If you want to ensure that a variable / function never ends up in the browser bundle, use //!no-bundle above it's declaration; which will throw a compiler error during build if it's included in the bundle.