Dynamic Rendering
Introduction
Dynamic rendering is the process of updating the visual representation of a component based on signals.
Rendering Dynamic Content
Dynamic content in sig is achieved by using some of the primitives provided by the library in combination with signals.
Ifcomponent: Renders its condition branch based on the signal value.Forcomponent: Render a list of items based on the signal that holds the list.Switch,Case&Defaultcomponents: Render the firstCasecomponent whoseconditionsignal is truthy, otherwise render theDefaultcomponent.Awaitcomponent: Render an asynchronous component or a lazy-loaded component (or both).
If Component
The If component is used to conditionally render content based on a signal value.
when the signal value is truthy the If component will render the then branch, otherwise it will render the fallback (else) branch.
import { createSignal, If } from 'sig';
function MenuButton() { const [isMenuOpen$, setIsMenuOpen] = createSignal(false); return (<button className="..." onClick={() => setIsMenuOpen((curr) => !curr)}> <If condition={isMenuOpen$} then={<X className="block h-6 w-6" />} fallback={<Menu className="block h-6 w-6" />} /> </button>);}
For Component
The For component is used to render a list of items based on a signal that holds the list.
Here is an example of how to use the For component to render a list of items:
The list display the filtered search result of the items based on the search term.
import { For, createSignal, combineLatest } from 'sig';
function SearchList() { const [items$, _setItems] = createSignal(['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple']); const [searchTerm$, setSearchTerm] = createSignal(''); const filteredItems$ = combineLatest([items$, searchTerm$]) .derive<string[]>(([items, searchTerm]) => !searchTerm ? items : items .filter((item) => item.toLowerCase() .includes(searchTerm.toLowerCase())) ); return (<div className="..."> <input value={searchTerm$} onInput={(e) => setSearchTerm(e.target.value)} placeholder="Search items" className="..." /> <For as='div' asProps={{ className: "..." }} list={filteredItems$} empty={<div>No items</div>} factory={(item) => <div>{item}</div>} /> </div>);}
For Item Indexing
The For component manages indexing of the rendered items in the list, it is crucial that the “index” (key) of each item is unique in order handle changes in the list’s items and order.
The For component will use the index to track the items in the list, and update the items in the list when the list changes.
By providing an index to the For component, you can specify how to generate the key for each item.
When index prop is provided :
- if it is a function, it will be called with the item and the index of the item in the list.
- if it is a string, it will be used as a property name to extract the key from the item object.
When no index prop provided, the default indexing behavior is the following:
- If the item is a primitive value, the index will be the item value.
- If the item is an object, the index will be the index of the item in the list.
Switch, Case & Default Components
The Switch, Case & Default components are used to render the first Case component whose condition signal is truthy, otherwise render the Default component.
Here is an example of how to use the Switch, Case & Default components to render a component based on the selected tab:
import { createSignal, Switch, Case, Default } from 'sig';
function TabContent() { const [selectedTab$, setSelectedTab] = createSignal('Tab1'); return (<div className="..."> <button onClick={() => setSelectedTab('Tab1')}>Tab1</button> <button onClick={() => setSelectedTab('Tab2')}>Tab2</button> <Switch condition={selectedTab$}> <Case value='Tab1'> <div>Tab1 content</div> </Case> <Case value='Tab2'> <div>Tab2 content</div> </Case> <Default> <div>No content</div> </Default> </Switch> </div>);}Await Component
The Await component is used to render an asynchronous component or a lazy-loaded component (or both).
Basically the Await component expect a component field, that can be a :
- An async component function.
- Function that return a Promise of an object with a default field with a value of a component function.
Lets focus first on the lazy loading part first, This is useful when you want to load a component only when it is needed, and not including it unnecessarily in the main build.
Combining the Await component with the import function, we’re getting a powerful pattern, to ship only the necessary code to the client.
Here is an example of how to use the Await component to render a lazy-loaded component:
import { Await, If } from 'sig';const lazyComponent = () => import('./LazyComponent');
function MainPage() { const [showLazyComponent$, setShowLazyComponent] = createSignal(false); return (<div> <button onClick={() => setShowLazyComponent((curr) => !curr)}>Toggle</button> <If condition={showLazyComponent$} then={<Await component={lazyComponent} /> </div>);}In the above example, the If component will render the Await component only when the showLazyComponent$ signal is truthy,
and when the Await component is rendered, it will load and render the LazyComponent component.
When inspecting the network tab in the browser, you will see that the LazyComponent build file, is loaded only when the button is clicked.
The Await component can also be used to render an asynchronous component, which is useful when you need to fetch data asynchronously before rendering the component.
Here is an example of how to use the Await component to render a component that fetches data asynchronously:
import { createSignal, Await } from 'sig';
async function AsyncUserCard() { const user = await fetch('/api/user').then((res) => res.json());
return (<div> <img src={user.avatar} alt={user.name} /> <div>{user.name}</div> </div>);}
function HomePage() { return (<div> <Await component={AsyncUserCard} /> </div>);}