Sign in
Log inSign up
Introduction to SolidJS and Reactive Primitives

Introduction to SolidJS and Reactive Primitives

First impressions on SolidJS

CanBS's photo
CanBS
·Oct 6, 2021·

9 min read

SolidJS is a true reactive library that allows you to use JSX for your frontend projects. In this blog post, I'll share my first impressions about the SolidJS UI library and its reactive primitives. The original article can be found here: "Introduction to SolidJS"

I like the concept of reactivity when building frontend projects. Despite its name, React is not a truly reactive library. I also like the Svelte because of its reactivity. I previously wrote a tutorial about Django and Svelte. However, I realized that writing projects with Svelte are not so scalable like React projects because React and JSX provide great modularity.

However, SolidJS offers the best of both worlds.

I'm currently not planning to do a real project with SolidJS until I use it. Currently, I'm building an e-commerce store, Duvar Posterleri (Wall Posters), and I would work with SolidJS for small scripts.

Introduction

OK, let's dive into the topic. Before reviewing SolidJS, it is better to get familiar with the concepts. I'll shortly talk about What are reactive systems? and what are those reactive primitives?.

What are reactive systems?

According to The Reactive Manifesto, reactive systems are responsive, resilient, elastic, and message-driven. We call these Reactive Systems.

Systems built as Reactive Systems are more flexible, loosely coupled, and scalable. This makes them easier to develop and amenable to change.

They are significantly more tolerant of failure, and when failure does occur they meet it with elegance rather than a disaster.

What Reactive Systems do

There are numerous reactive libraries in many programming languages like SolidJS in JS.

Reactive systems must react to data change. Generally, these changes occur when new data receives or old updates.

Characteristics of Reactive Programming

The reactive manifesto defines the key characteristics of it like that:

  • Responsive: Those systems respond in a timely manner. Here of course timely will differ depending upon the application and domain.
  • Resilient. Reactive systems stay responsive in the face of failure.
  • Elastic. As the workload grows the system should continue to be responsive.
  • Message Driven. Information is exchanged between elements of a reactive system using messages. This ensures loose coupling, isolation and location transparency between these components.

What are the reactive primitives of SolidJS?

In SolidJS, the author of the library Ryan Carniato defines them as much like network primitives rather than JavaScript's primitives. As you will see later, Signals are basically observables.

Installation of SolidJS Template

You can easily install a starter SolidJS template with degit. You can also check other official templates from here: SolidJS Official Templates. I prefer a JS template rather than a TypeScript.

# Javascript template
npx degit solidjs/templates/js solid
cd solid

# install the dependencies
yarn install

The template uses Vite as a development tool. Also, this is the first time I have used Vite. Vite is so super fast that I had to check twice that if it reloaded the rendered page. When the installation is done, the project directory looks like that:

SolidJS directory

It is very similar to React in many cases. I will check some component rendering processes'.

In this post, I am going to explore SolidJS in an introductory manner. I'll also create a Counter component first and check its re-render process.

A Reactive JavaScript Library: SolidJS

A) Reactive primitives: createSignal

SolidJS has some basic reactive primitives, and Signals are one of them. It looks like it is a "useState" alternative of React Hooks. One difference to the "useState" hook is that a Signal returns two functions: a getter and a setter. Here is the official example of creating a signal:

  • createSignal function takes an initial value and returns an array with an access and update function.
  • You should execute the getter function (access) in order to get the value.
  • You can pass function to update function (set function). In this function, you can access the previous state also.
const [getValue, setValue] = createSignal(initialValue);

// read value
getValue();

// set value
setValue(nextValue);

// set value with a function setter
setValue((prev) => prev + next);

Solid JS createSignal

import { createSignal } from "solid-js";

function Counter({ initial }) {
    const [count, setCount] = createSignal(initial || 0);

    return (
        <div>
        {/* Notice the usage of count! It is a function*/}
            <h2>Count: {count()}</h2>
        </div>
    );
}

1) Component State Access and Update

SolidJS call the state elements as signals. However, I prefer to use state rather than signal. Let's make a Counter component within the App component. Fill the App.jsx file as follows:

SolidJS change state

import logo from "./logo.svg";
import styles from "./App.module.css";
import { createSignal } from "solid-js";

function App() {
    /**
     * CHECKPOINT
     * if the App component renders
     * it will print to console
     */
    //
    console.log("App component rendered.");

    return (
        <div class={styles.App}>
            <header class={styles.header}>
                <img src={logo} class={styles.logo} alt="logo" />
                <p>
                    Edit <code>src/App.jsx</code> and save to reload.
                </p>
                <a
                    class={styles.link}
                    href="github.com/solidjs/solid"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn Solid
                </a>
                <Counter />
            </header>
        </div>
    );
}

function Counter({ initial }) {
    const [count, setCount] = createSignal(initial || 0);

    /**
     * CHECKPOINT
     * if the Counter component renders. it will print to console.
     * Also, I put another print statement for the count function.
     */
    //
    console.log("Counter component rendered.");
    console.log("Counter component count value: ", count());

    return (
        <div style={{ width: "100%", height: "auto" }}>
            {/* Notice the usage of count! It is a function*/}
            <h2>Count: {count()}</h2>
            <button onClick={() => setCount((c) => c + 1)}>Increase</button>
            <button onClick={() => setCount((c) => c - 1)}>Decrease</button>
        </div>
    );
}

export default App;

Let's check the browser and the first render of SolidJS. As you see, there is no extra component render. If it were React, we should have seen many times "Counter component rendered" text on the console.

SolidJS's Reactivity

2) Parent Component State Access and Update

Let's make it further and pass the signal setter to the child component and use it from there. Change both App and Counter components like that:

SolidJS change parent state

function App() {
    /**
     * CHECKPOINT
     * if the App component renders
     * it will print to console
     */
    //
    const [appCount, setAppCount] = createSignal(0);
    console.log("App: count: ", appCount());
    console.log("App component rendered.");

    return (
        <div class={styles.App}>
            <header class={styles.header}>
                <img src={logo} class={styles.logo} alt="logo" />
                <p>
                    Edit <code>src/App.jsx</code> and save to reload.
                </p>
                <a
                    class={styles.link}
                    href="github.com/solidjs/solid"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn Solid
                </a>

                {/* NEW */}
                <h2>App Count: {appCount()}</h2>

                <Counter
                    initial={appCount()}
                    setAppCount={setAppCount} // NEW
                />
            </header>
        </div>
    );
}
function Counter({ initial, setAppCount }) {
    const [count, setCount] = createSignal(initial || 0);

    /**
     * CHECKPOINT
     * if the Counter component renders. it will print to console.
     * Also, I put another print statement for the count function.
     */
    //
    console.log("Counter component rendered.");
    console.log("Counter component count value: ", count());

    return (
        <div style={{ width: "100%", height: "auto" }}>

            {/* Notice the usage of count! It is a function*/}
            <h2>Count: {count()}</h2>
            <button onClick={() => setCount((c) => c + 1)}>Increase</button>
            <button onClick={() => setCount((c) => c - 1)}>Decrease</button>
            <hr />

            {/* Buttons changes the signal value of its parent component */}
            <button onClick={() => setAppCount((c) => c + 1)}>
                AppCount Increase
            </button>
            <button onClick={() => setAppCount((c) => c - 1)}>
                AppCount Decrease
            </button>
        </div>
    );
}

As you see, there is not any component re-render. It's awesome.🥳

B) Reactive primitives: createEffect

As you supposed, createEffect is the equivalent of the useEffect hook of React. The official explanation and example are as follows:

Creates a new computation that automatically tracks dependencies and runs after each render where a dependency has changed. Ideal for using refs and managing other side effects.

const [a, setA] = createSignal(initialValue);

// effect that depends on signal `a`
createEffect(() => doSideEffect(a()));

It's time to play with this function. The official example returns a function (doSideEffect) that takes state value as its argument. Even if the returning function doesn't take the state value as its argument but as an inner value, the createEffect function successfully makes a side-effect.

Let's add those to the App component.

    // The function creates side-effect
    const changeTitle = (val) => (window.document.title = `#App: ${val}`);

    // effect that depends on signal `a`
    createEffect(() => changeTitle(appCount()));

We created a function (changeTitle) responsible for the side-effect. It takes a value and changes the document title according to that. It also takes the state value of the App component which is appCount. Your app component should look like this.

function App() {
    const [appCount, setAppCount] = createSignal(0);
    console.log("App: count: ", appCount());
    console.log("App component rendered.");

    // The function creates side-effect
    const changeTitle = (val) => (window.document.title = `#App: ${val}`);

    // effect that depends on signal `a`
    createEffect(() => changeTitle(appCount()));

    return (
        <div class={styles.App}>
            <header class={styles.header}>
                <img src={logo} class={styles.logo} alt="logo" />
                <p>
                    Edit <code>src/App.jsx</code> and save to reload.
                </p>
                <a
                    class={styles.link}
                    href="github.com/solidjs/solid"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn Solid
                </a>

                {/* NEW */}
                <h2>App Count: {appCount()}</h2>

                <Counter
                    initial={appCount()}
                    setAppCount={setAppCount} // NEW
                />
            </header>
        </div>
    );
}

You'll easily differentiate that when the app renders the first time, the document title was App: 0

After, when I clicked and increased the appCount value, the document title also changed to the corresponding value. You'll also notice that there will be no component re-render.

SolidJS createEffect

C) Reactive primitives: createMemo

This reactive primitive returns a function that returns a read-only derived signal. Its value is recalculated whenever the dependencies are updated. createMemo primitive is the equivalent of useMemo hook.

Edt the App component according to those:

    // Add those to the App component
    // It recalculate the value whenever the dependencies are updates.
    const makeDouble = (val) => val * 2
    const doubleCount = createMemo(() => makeDouble(appCount()))
    console.log("doubleCount ", doubleCount());

Also, update the content of the App component. By doing this, we can see the doubleCount signal in work. You can also check the code location from the image below.

<h2>Double Count: {doubleCount()}</h2>

SolidJS createMemo function

D) Reactive primitives: createResource

This function creates a signal that is responsible for async requests. The official explanation and example are here:

Creates a signal that can manage async requests. The fetcher is an async function that accepts the return value of the source if provided and returns a Promise whose resolved value is set in the resource. The fetcher is not reactive so use the optional first argument if you want it to run more than once. If the source resolves to false, null, or undefined will not fetch. Also, loading and error are reactive getters and can be tracked.

const [data, { mutate, refetch }] = createResource(getQuery, fetchData);

// read value
data();

// check if loading
data.loading;

// check if errored
data.error;

// directly set value without creating promise
mutate(optimisticValue);

// refetch last request just because
refetch();

My first impressions about SolidJS are amazing. Up to this point, there is no overhead that you always face with React. I will be watching the development of SolidJS with interest.