visit
import { render } from "solid-js/web";
import { onCleanup, createSignal } from "solid-js";
function App() {
const [count, setCount] = createSignal(0);
const interval = setInterval(() => setCount((count) => count + 1), 1000);
onCleanup(() => clearInterval(interval));
return <div>Counter: {count()}</div>;
}
render(() => <App />, document.getElementById("app"));
In a Solid application, components are functions that return JSX elements. Class components are not supported. Note that JSX code is compiled into functions that directly update the DOM (since Solid doesn’t use a Virtual DOM). To avoid recreating DOM nodes on every update, Solid provides several components for conditional and looping that we should use instead of
if/else
, switch
statements and Array.prototype.map
. The most important components are Show
, Switch
and For
:<Show
when={loggedIn()}
fallback={<button onClick={toggle}>Log in</button>}
>
<button onClick={toggle}>Log out</button>
</Show>
<Switch fallback={<p>Normal temperature</p>}>
<Match when={temp() >= 40}>
<p>Too hot</p>
</Match>
<Match when={temp() <= 10}>
<p>Too cold</p>
</Match>
</Switch>
<For each={articles()}>{(a, index) =>
<li>{index() + 1}: {a.title}</li>
}</For>
Likewise, to avoid recreating children nodes on every update, you should use the
children
helper which basically creates a memo around the children prop:function MyComponent(props) {
const c = children(() => props.children);
return (
<div>
{c()}
</div>
);
}
The cornerstones of reactivity in Solid are signals and effects which look somewhat similar to React’s
useState
and useEffect
hooks:import { createSignal, createEffect } from "solid-js";
function App() {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("Count: ", count());
});
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
However, signals are vastly different from the
useState
hook in the following aspects:useState()
from within a function component or a custom hook, you can call createSignal()
from anywhere. If called within a component, the signal represents that component’s local state. Otherwise, the signal represents an external state that any component can import and use to render their UI.createSignal()
is not the data itself but a getter function. When the getter function is called, the calling function (obtained from a global stack) will be added to the signal’s subscribers list.Similar to React's
useEffect
hook, createEffect()
defines a side effect that should run whenever a signal it depends on changes. However, thanks to Solid’s automatic dependency tracking, you don’t have to explicitly provide a dependency list.With React, your component function reruns whenever the component’s state changes. In contrast, Solid component functions never rerun. A component runs only once to create the necessary signals and effects (JSX code is compiled into an effect as well). After that the component vanishes. That means we don’t have access to component lifecycle events like we do with React or other libraries.
However, Solid does provide two special events called
onMount
and onCleanup
. onMount
can be considered a special effect that runs only once, after all initial rendering is done. The most common use case is fetching data when a screen is loaded.import { createSignal, onMount } from "solid-js";
function App() {
const [data, setData] = createSignal();
onMount(async () => {
const res = await fetch(`/path/to/your/api`);
setData(await res.json());
});
return (/* JSX to render UI based on data */);
}
onCleanup
can be called in a component (see the first example above), in an effect (example below), or at any scope that is part of the synchronous execution of the reactive system. onCleanup
will run when that scope is disposed or re-evaluated.import { createSignal, createEffect, onCleanup } from "solid-js";
function App() {
const [counting, setCounting] = createSignal(false);
const [count, setCount] = createSignal(0);
createEffect(() => {
if (counting()) {
const c = setInterval(() => setCount((val) => val + 1), 300);
onCleanup(() => clearInterval(c));
}
});
return (
<div>
<button type="button" onClick={() => setCounting((val) => !val)}>
{counting() ? "Stop" : "Start"}
</button>
<p>Counter: {count()}</p>
</div>
);
}
However, there is a caveat. Generally, you should not destructure props. By doing so you will loose reactivity, meaning that the child component’s UI will not update when prop values change. As compensation, Solid provides two helpers for working with props:
mergeProps()
and splitProps()
.// DON'T do this
function Greeting({ name, greeting = "Hi" }) {
return <h3>{greeting}, {name}!</h3>
}
// use mergeProps() to set default values
function Greeting(props) {
const merged = mergeProps({ greeting: "Hi" }, props);
return <h3>{merged.greeting}, {merged.name}!</h3>
}
// DON'T do this
export default function Greeting(props) {
const { greeting, name, ...others } = props;
return <h3 {...others}>{greeting}, {name}!</h3>
}
// use splitProps() instead of the rest syntax
function Greeting(props) {
const [local, others] = splitProps(props, ["greeting", "name"]);
return <h3 {...others}>{local.greeting}, {local.name}!</h3>
}
function App() {
const [name, setName] = createSignal("World");
return (
<div>
<input
type="text"
value={name()}
onInput={(evt) => setName(evt.currentTarget.value)}
/>
<p>Hello, {name()}!</p>
</div>
);
}
Using refs in a Solid application is not much different from that with React. Basically, you can either declare a local variable and assign it to a prop named
ref
, or use a callback:// local variable
function SimpleForm() {
let ref;
onMount(() => ref.focus());
return (<input ref={ref} />);
}
// ref callback
function SimpleForm() {
return (
<input ref={el => {
onMount(() => el.focus())
}} />
);
}
Another idea that Solid borrows from React is error boundary components. However, you don’t have to implement it manually as
ErrorBoundary
is a builtin component in Solid:import { ErrorBoundary } from "solid-js";
<ErrorBoundary fallback={err => {
// report error
console.log(err);
// fallback UI
return (/* JSX */)
}}>
{/* your component tree */}
</ErrorBoundary>