Get a first-hand look at the simple and powerful approach to reactive front-end JavaScript that is fast making Solid a favorite.
SolidJS is a unique approach to reactive front-end JavaScript. It provides a compact set of reactive “primitives” and leverages those to support higher-order functionality. This sounds somewhat like AngularJS and its use of ReactiveX, but it is quite a different experience. SolidJS feels more like the simplicity of AlpineJS, but delivers a far more ambitious set of capabilities.
SolidJS just hit its 1.0 release. Let’s take a look.
Setting up SolidJS
First, set up a simple starter project. On your local machine, use the commands in Listing 1 to start something new (this follows the SolidJS docs).
Listing 1. Creating a new SolidJS project
> npx degit solidjs/templates/js my-app
> cd my-app
> npm i # or yarn or pnpm
> npm run dev # or yarn or pnpm
Now you have a simple SolidJS app up at running. If you go to http://localhost:3000 you’ll see the landing page.
Note that the dev server supports on-the-fly updates (including Hot Module Replacement, HMR). So as you make changes to files, they will automatically update what is displayed in the browser.
Fetching a remote API with createResource
Let’s begin with Solid’s createResource
. This is a simple layer atop createSignal
, which is Solid’s reactive primitive that handles asynchronous communication with remote APIs. We are going to start here because this feature is a key hinge in Solid’s support of Suspense/Concurrent rendering, and it can seem a bit obscure at first.
In a way, createResource
is the middle layer of Solid’s architecture.
Open the newly created folder in your IDE and open the App.jsx file. This file is loaded as the root node by a call to Solid’s render()
method in index.jsx.
Make the changes that I’ve commented in Listing 2.
Listing 2. Modifying App.jsx
import logo from "./logo.svg";
import styles from "./App.module.css";
import { createResource } from "solid-js"; // Import the createResource function from solid
// The next line sets up the actual async function that will do the request. It is a simple wrapper around a fetch call that returns a promise - the promise returned by the call to
// fetch.json(). This function is used by the call to createResource below
const fetchJokes = async (id) => (await fetch(`https://official-joke-api.appspot.com/jokes/programming/ten`)).json();
function App() {
const [jokes] = createResource(fetchJokes); // Here we use createResource to associate from the fetchJokes promise to the results: the jokes variable
return (
<div class={styles.App}>
<!-- The line below uses the built-in loading state that Solid has provided on the jokes variable, initialized above with a call to createResource -->
<span>{jokes.loading && "Loading..."}</span>
<div>
<!-- This line below simply outputs the contents of the jokes var. This will not happen until jokes.loading == true. >
<jokes() -->
<pre>{JSON.stringify(jokes(), null, 2)}
Edit src/App.js
and save to reload.
The comments in Listing 2 should clue you into what is going on here. The result of this code is the output of a JSON array that contains a set of 10 random programming jokes.
Iterating with >For
Now let’s take that JSON and iterate over it, creating more useful output.
First, include the For
component by adding it to the imports:
import { createResource, For } from "solid-js";
Next, replace the call to JSON.stringify with Listing 3.
Listing 3. Iterating over array
<ul>
<For each={jokes()}>{(joke) =>
<li>{joke.id}: {joke.setup}</li>
}</For>
</ul>
Listing 3 gives you a look at the For
component in action. It is similar to other reactive frameworks. You tell it what collection to iterate over (i.e., jokes()
, remembering it is a method call, not a reference), and then define an anonymous function whose argument is the iteration variable, joke
.
We use this here to output an unordered list that displays each joke’s ID and setup.
Handling events in SolidJS
Up next is getting a look at the punch line for each joke. For this, you’ll need an onClick
event handler that will display an alert containing the punch line.
Listing 4 shows how to add the handler.
Listing 4. onClick handler
<li onClick={()=>{alert(joke.punchline)}}>
{joke.id}: {joke.setup}
</li>
Simple enough. Listing 4 is very typical of reactive frameworks. Now when I click on “Where do programmers like to hang out?” I receive the answer, “The Foo Bar.” That is so bad it is funny.
Making reactive variables with createSignal
Now imagine that you want to add the ability to toggle between showing programming jokes and all jokes. The remote API handles this by the presence or absence of “programming” in the penultimate part of the URL path (…/jokes/programming/ten versus …/jokes/ten).
So let’s add a checkbox to the top of the page to let the user change this. The first thing we’ll do is create a “signal.” This is the most fundamental reactive primitive in SolidJS. It allows you to specify a variable, a setter, and an optional default value. The syntax is inspired by the useState
syntax in React.
Above the createResource
call, add this line:
const [jokeType, setJokeType] = createSignal("");
This creates a new signal called jokeType
, with an initial value of an empty string. Signal values are accessed as a function: jokeType()
.
Now insert the checkbox element shown in Listing 5 at the head of the main div.
Listing 5. A reactive checkbox
Programming Only: <input type="checkbox" checked={jokeType()=="programming/"}
onInput={()=>{setJokeType(jokeType()==''?'programming/':'')}}> </input>
Listing 5 is mostly normal HTML. The checked
and onInput
attributes have Solid-specific content. checked
uses a token to check the value of the jokeType()
signal against "programming/"
. That is to say, the box is checked if the value of jokeType
is "programming/"
.
onInput
handles the input event on the checkbox. When it fires, we change the value of jokeType
so that it swaps between an empty string and "programming/"
. We are going to use this changing value in the URL of the joke fetcher.
Using the signal in the resource
First, you must pass the signal into the call to createResource
as an argument. Solid is smart enough to detect this and will wire the resource up reactively. The new call to createResource
looks like this:
const [jokes] = createResource(jokeType, fetchJokes);
(Thanks to Ryan Carniato for clarifying this.)
Notice that jokeType
is passed directly (not a function call, but the function itself). This is because the actual signal is being passed to createResource
, allowing it to be reactive.
The new joke fetcher function is seen in Listing 6.
Listing 6. Reactive resource with signal argument
const fetchJokes = async (jokeType) => {
return (await fetch(`https://official-joke-api.appspot.com/jokes/${jokeType}ten`)).json();
}
Notice that the jokeType
signal is a straight variable in the argument of Listing 6. The fetch URL makes use of the value of jokeType
. When the signal is changed via the checkbox, Solid will notice and automatically re-fetch the list of jokes.
Computed effects with createEffect
Reactive libraries include the ability to wrap simple reactive variables (like createSignal
in SolidJS, or useState
in React) in what is known as an effect (e.g., useEffect
in React). Solid provides createEffect
for this purpose. The central idea is that you use an effect to handle non-pure actions (aka, side effects). Such actions usually mean modifying some part of the UI directly as a result of changes to the variables they depend on.
Effects are a bit hard to understand at first. A good way to think about it is that an effect will do something, as opposed to just responding to a value change.
Let’s say you wanted to log when the user changes the joke type. This is a contrived example to communicate the essence. Listing 7 shows how you would accomplish it.
Listing 7. createEffect example
createEffect(() => {
console.log('Joke Type is now: ', jokeType())
})
Now the console will output as the signal changes. Notice that the effect executes initially when the isgnal is set.
It’s tough to predict where the world of JavaScript is headed, but it is clear that SolidJS is pushing the envelope, pursuing promising leads and exploring new territory. If the interest in SolidJS continues to grow at the current pace, it will be a major player in the field in short order.