Exploring SolidJS - The Reactive Primitives (1)
Can Burak Sofyalioglu
Posted on October 6, 2021
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 on 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.
Shameless promotion
I'm currently not planning to do a real project with SolidJS until I become fluent in it. Currently, I'm building an e-commerce store, İzmir Güvenlik Kamerası (Security Camera Systems) and Fine Art Print Store, and I would work with SolidJS for small projects. You can also check the list of blogging sites
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 changes. Generally, these changes occur when new data is received or the old one is updated.
Characteristics of Reactive Programming
The reactive manifesto defines the key characteristics of it, like that:
- Responsive: Those systems respond on time. 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:
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);
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:
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="https://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.
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:
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="https://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 can see, there is not any component re-rendering. It's awesome.🥳
B) Reactive primitives: createEffect
As you might expect, createEffect
is the equivalent of the useEffect
hook in 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 ref
s 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="https://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.
C) Reactive primitives: createMemo
This reactive primitive returns a function that returns a read-only derived signal. Its value is recalculated whenever 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>
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 of 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.
Posted on October 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.