JavaScript Video Tutorial: Discovering SolidJS

In this video I invite you to discover SolidJS, a JavaScript library that allows you to create responsive user interfaces.

00:00 Presentation
19:50 Asynchronous management
29:11 My review
30:40 Cons

The peculiarity of SolidJS

If we look at the first examples of the documentation we can think that SolidJS is only a simple clone of React (a bit like Preact).

import { createSignal } from "solid-js";

export const Compteur = () => {
    const [count, setCount] = createSignal(0);
    return <div>
        <p>Compteur : {count()}</p>
        <button onClick={() => setCount(n => n+ 1)}>Incrémenter</button>
    </div>;
};

But the way of working internally is completely different.

No virtual dom

The first big particularity is the non-use of the virtual-DOM. The JSX is converted thanks to a particular module babel-preset-solid (a vite plugin exists vite-plugin-solid) which will convert the code to generate HTML elements directly. For example, we can write this:

document.body.appendChild(<div>Hello World</div>)

If the component uses reactive elements, the modifications will be made directly on the DOM without any intermediate step, which allows for much better performance. The approach here is similar to that used in Svelte.

The reactivity

The other particularity is the way SolidJS manages responsiveness. For this Solid uses a signal system.

const [count, setCount] = createSignal(0)

This method returns an array of 2 elements:

  • A getter which, when called, returns the current value. If this getter is called in a tracking context (createEffect for example) then the calling function will be re-executed when the Signal is modified.
  • A setter that allows the value inside the signal to be changed and informs the tracking contexts of the change.
// La fonction sera re-appelée à chaque fois que count sera modifié
createEffect(() => {
    console.log(count())
})

On the other hand, this reactivity can be made difficult for objects for which it is necessary to use nested signals. To avoid too much cumbersome SolidJS has a store system that allows you to create a proxy that will generate a signal tree automatically.

const [todos, setTodos] = createStore([
  {
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  // ...
])

function addTodo (title) {
  setTodos([{title, id: Date.now(), completed: false}, ...todos])
}

function removeTodo (todo) {
  setTodos(todos.filter(t => t.id !== todo.id))
}

function toggleTodo (todo) {
  // On peut aussi spécifier le chemin
  setTodos(
}

The store setter allows you to directly modify a value within the object by specifying the path to the property to modify, which greatly simplifies certain operations.

Asynchronous management

Another interesting aspect of SolidJS is its native multi-level asynchronous support. It is for example possible to load a component asynchronously thanks to the method lazy().

import { lazy } from "solid-js"
const ComposantLourd = lazy(() => import("./ComposantLourd"));

function App() {
  return (
    <>
      <h1>Welcome</h1>
      <ComposantLourd name="Jake" />
    </>
  );
}

You can also use the method createResource() which makes it easy to manage data retrieved asynchronously.

import { createSignal, createResource } from "solid-js"

const fetchTodos = (page) => fetch(`https://jsonplaceholder.typicode.com/todos?_limit=5&_page=${page}`).then(r => r.json())

function App () {
  const [page, setPage] = createSignal(1)
  const [todos] = createResource(page, fetchTodos)

  return (
    <>
      <ul>
        <For each={todos}>
          {todo => <TodoItem todo={todo}/>}
        </For>
      </ul>
      <button onClick={setPage(n => n + 1)}>Page suivante</button>
    </>
  )
}

This method can also be coupled with a component <Suspense> which displays a component while loading data. Component that can also be coupled with a transition system to add a loading effect when new data arrives.

import { createSignal, createResource } from "solid-js"

function App () {
  const [page, setPage] = createSignal(1)
  const [todos] = createResource(page, fetchTodos)

  return (
    <>
      <Suspense fallback={<p>Chargement...</p>}>
        <ul>
          <For each={todos}>
            {todo => <TodoItem todo={todo}/>}
          </For>
        </ul>
        <button onClick={setPage(n => n + 1)}>Page suivante</button>
      </Suspense>
    </>
  )
}

The inconvenients

The choices made by SolidJS are also not void of compromise. Tracking signal value changes can only be done in a particular context (jsx Where createEffect) and in some situations this follow-up is not done as one might expect. This happens for example if we use destructuring.

function Salutation (props) {
    const name = props.name
    return <div>
        <p>{props.name} sera mis à jour quand la props change</p>
        <p>{name} ne sera jamais mis à jour car la destructuration a perdu le contexte de suivi</p>
    </div>
}

Instead of finding yourself looking for the cause of useless rendering like in React, we find ourselves here looking for why a change is not followed correctly. It is therefore necessary to understand the internal functioning of the library to avoid this kind of concern, sometimes even writing code that does not necessarily seem natural.

The second problem is the misuse of a classic syntax (JSX) which requires a specific module which can affect compatibility with certain bundlers like esbuild.

Conclusion

Despite these disadvantages SolidJS remains an interesting library to create specific interface elements thanks to a light weight and optimal performance with its approach closer to the DOM. It will still be necessary to pay attention to the way it manages responsiveness to see if it does not lead to too many surprises compared to the other alternatives.