Solid

date
Jan 1, 2023
type
KnowledgeBase
year
slug
solid
status
Published
tags
JavaScript
summary
Solid is very similar to React, but much faster. It uses JSX, but has no virtual DOM. This is mostly a condensed version of the tutorial on the official site.

Basics

ℹ️
Very similar to React, but much faster! Uses JSX, but has no virtual DOM
import { render } from 'solid-js/web'; function HelloWorld() { return <div>Hello World!</div>; } render(() => <HelloWorld />, document.getElementById('app'))

Signals

ℹ️
Signals are values that change over time. Similar to state in React.
import { render } from "solid-js/web"; import { createSignal } from "solid-js"; function Counter() { const [count, setCount] = createSignal(0); setInterval(() => setCount(count() + 1), 1000); return <div>Count: {count()}</div>; } render(() => <Counter />, document.getElementById('app'));
Notice how count() is a function call! (unlike how you’d do it in React)

Derived Signals

Whenever we access a signal in JSX, it will automatically update the view when that signal changes. But the component function itself only executes once.
A function that accesses a signal is effectively also a signal: when its wrapped signal changes it will in turn update its readers.
doubleCount in the example below is such a derived signal - it doesn’t store a value itself, but gains reactivity from the signal it accesses!
import { render } from "solid-js/web"; import { createSignal } from "solid-js"; function Counter() { const [count, setCount] = createSignal(0); const doubleCount = () => count() * 2; setInterval(() => setCount(count() + 1), 1000); return <div>Count: {doubleCount()}</div>; } render(() => <Counter />, document.getElementById('app'));

Effects

ℹ️
Effects are observers that can be updated by Signals. Effects automatically subscribe to any signals that are being read during the function’s executions and reruns when any of them change.
import { render } from 'solid-js/web'; import { createSignal, createEffect } from 'solid-js'; function Counter() { const [count, setCount] = createSignal(0); createEffect(() => { console.log("The count is now", count()); }); return <button onClick={() => setCount(count() + 1)}>Click Me</button>; } render(() => <Counter />, document.getElementById('app'));

Memos

ℹ️
createMemo allows you to cache values in order to reduce duplicate work.
We can use memos to evlauate a function and store the result until its dependencies change. (Great for caching calculations, etc.)
import { render } from 'solid-js/web'; import { createSignal, createMemo } from 'solid-js'; function fibonacci(num) { if (num <= 1) return 1; return fibonacci(num - 1) + fibonacci(num - 2); } function Counter() { const [count, setCount] = createSignal(10); const fib = createMemo(() => { console.log('Calculating Fibonacci'); return fibonacci(count()); }); return ( <> <button onClick={() => setCount(count() + 1)}>Count: {count()}</button> <div>1. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>2. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>3. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>4. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>5. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>6. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>7. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>8. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>9. {fib()} {fib()} {fib()} {fib()} {fib()}</div> <div>10. {fib()} {fib()} {fib()} {fib()} {fib()}</div> </> ); } render(() => <Counter />, document.getElementById('app'))

Control Flow

ℹ️
(a ? b : c) and (a && b) work just fine, but Solid has a more readable way too: <Show>

Show

<Show> if basically an if statement. Use the when as the condition and fallback as an else.
import { render } from 'solid-js/web'; import { createSignal, Show } from 'solid-js'; function App() { const [loggedIn, setLoggedIn] = createSignal(false); const toggle = () => setLoggedIn(!loggedIn()) return ( <Show when={loggedIn()} fallback={<button onClick={toggle}>Log in</button>} > <button onClick={toggle}>Log out</button> </Show> ); } render(() => <App />, document.getElementById('app'))

For

<For> is the best way to loop over an array of objects. Pass the array to each, then you get a callback with the element as the first argument and the index as the second. (Note: index is a Signal, not a constant number!)
import { render } from 'solid-js/web'; import { createSignal, For } from 'solid-js'; function App() { const [cats, setCats] = createSignal([ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]); return ( <ul> <For each={cats()}>{(cat, i) => <li> <a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}> {i() + 1}: {cat.name} </a> </li> }</For> </ul> ); } render(() => <App />, document.getElementById('app'))

Index

Use <Index> instead of <For> when iterating over primitives (like strings and numbers).
<For> cares about each piece of data in your array, and the position of that data can change; <Index> cares about each index in your array, and the content at each index can change.
import { render } from 'solid-js/web'; import { createSignal, Index } from 'solid-js'; function App() { const [cats, setCats] = createSignal([ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]); return ( <ul> <Index each={cats()}>{(cat, i) => <li> <a target="_blank" href={`https://www.youtube.com/watch?v=${cat().id}`}> {i + 1}: {cat().name} </a> </li> }</Index> </ul> ); } render(() => <App />, document.getElementById('app'))

Switch and Match

import { render } from "solid-js/web"; import { createSignal, Switch, Match } from "solid-js"; function App() { const [x] = createSignal(7); return ( <Switch fallback={<p>{x()} is between 5 and 10</p>}> <Match when={x() > 10} > <p>{x()} is greater than 10</p> </Match> <Match when={5 > x()}> <p>{x()} is less than 5</p> </Match> </Switch> ); } render(() => <App />, document.getElementById("app"));

Dynamic

<Dynamic> can be used to render from data. Pass a string or a component function.
It lets us replace this <Switch> statement:
<Switch fallback={<BlueThing />}> <Match when={selected() === 'red'}><RedThing /></Match> <Match when={selected() === 'green'}><GreenThing /></Match> </Switch>
with this:
<Dynamic component={options[selected()]} />
Full example:
import { render, Dynamic } from "solid-js/web"; import { createSignal, For } from "solid-js"; const RedThing = () => <strong style="color: red">Red Thing</strong>; const GreenThing = () => <strong style="color: green">Green Thing</strong>; const BlueThing = () => <strong style="color: blue">Blue Thing</strong>; const options = { red: RedThing, green: GreenThing, blue: BlueThing } function App() { const [selected, setSelected] = createSignal("red"); return ( <> <select value={selected()} onInput={e => setSelected(e.currentTarget.value)}> <For each={Object.keys(options)}>{ color => <option value={color}>{color}</option> }</For> </select> <Dynamic component={options[selected()]} /> </> ); } render(() => <App />, document.getElementById("app"));

Portal

The <Portal> component’s child content will be inserted at the location of your choosing. (per default in a <div> in document.body.
import { render, Portal } from "solid-js/web"; import "./styles.css"; function App() { return ( <div class="app-container"> <p>Just some text inside a div that has a restricted size.</p> <Portal> <div class="popup"> <h1>Popup</h1> <p>Some text you might need for something or other.</p> </div> </Portal> </div> ); } render(() => <App />, document.getElementById("app"));

Error Boundary

<ErrorBoundary>s catch JavaScript errors anywhere in their child component tree, log them and display fallback UI.
import { render } from "solid-js/web"; import { ErrorBoundary } from "solid-js"; const Broken = (props) => { throw new Error("Oh No"); return <>Never Getting Here</> } function App() { return ( <> <div>Before</div> <ErrorBoundary fallback={err => err}> <Broken /> </ErrorBoundary> <div>After</div> </> ); } render(() => <App />, document.getElementById("app"));

Lifecycles

Everything lives and dies by the reactive system.

onMount

onMount is just a createEffect call that is non-tracking (it never re-runs) - will only run once for your component once all initial rendering is done.
import { render } from "solid-js/web"; import { createSignal, onMount, For } from "solid-js"; import "./styles.css"; function App() { const [photos, setPhotos] = createSignal([]); onMount(async () => { const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`); setPhotos(await res.json()); }); return <> <h1>Photo album</h1> <div class="photos"> <For each={photos()} fallback={<p>Loading...</p>}>{ photo => <figure> <img src={photo.thumbnailUrl} alt={photo.title} /> <figcaption>{photo.title}</figcaption> </figure> }</For> </div> </>; } render(() => <App />, document.getElementById('app'));

onCleanup

You can call onCleanup at any scope and it will run when that scope is triggered to re-evaluate and when it is finally disposed. Use in your components, in your Effects or in your custom directives.
import { render } from "solid-js/web"; import { createSignal, onCleanup } from "solid-js"; function Counter() { const [count, setCount] = createSignal(0); const timer = setInterval(() => setCount(count() + 1), 1000); onCleanup(() => clearInterval(timer)); return <div>Count: {count()}</div>; } render(() => <Counter />, document.getElementById('app'));

Bindings

Events

 

Leave a comment