DEV Community

Cover image for SolidJs the new React, but better 😎
Tauan Tathiell
Tauan Tathiell

Posted on

SolidJs the new React, but better 😎

Introduction

I started working professionally with react about 4 years ago, I had the pleasure of seeing this library become what it has become today, before we had to create smart components extending the Component class of react, then we had the introduction of hooks where when instead of using class components we used function components with the [useState, useEffect, useMemo, useContext, useReducer] hooks, this made the verbosity of the code slightly decreased.

"OK, but isn't this post about SolidJs?"

To talk about solid-js we have to give a context of how things are done in react.

Here's an example using react Hooks for a simple counter component.

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setInterval(() => {
      setCount(count + 1)
    }, 1000)
  })

  return <div>Count: {count}</div>
}
Enter fullscreen mode Exit fullscreen mode

"But wait, this useEffect keeps popping me a warning", yes, it will say that a dependency is missing in the Array dependency of useEffect, let's add it to stop the warning.

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setInterval(() => {
      setCount(count + 1)
    }, 1000)
  }, [count])

  return <div>Count: {count}</div>
}
Enter fullscreen mode Exit fullscreen mode

Let's run the project:

Re-run glitch

But now we face another problem, after a few years working with react we started to fight this problem daily, the famous re-run, we can solve this re-run problem in the Counter component in a few ways:

  • Returning from useEffect a function that clears the setInterval
  • Using setTimeout instead of setInterval (a great practice but the above approach would be necessary to clean the function)
  • Using the function itself to return the previous value directly as the current value

Let's use the last option here:

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setInterval(() => {
      setCount(prevCount => prevCount + 1)
    }, 1000)
  }, [])

  return <div>Count: {count}</div>
}
Enter fullscreen mode Exit fullscreen mode

We came up with an idea that react has a "false reactivity" 🧐 .

Let's talk a little about SolidJS

First of all, solid-js is not trying to re-invent the wheel, solid-js is identical to react, let's create our Counter component using solid-js.

function Counter() {
  const [count, setCount] = createSignal(0)

  setInterval(() => {
    setCount(count() + 1)
  }, 1000)

  console.log('the counter called!')

  return <div>Count: {count()}</div>
}
Enter fullscreen mode Exit fullscreen mode

We see a big difference here, count in solid is a function. in solid this is called accessor and this is one of the mystical things behind how solid works. Okay, we noticed in react that we have to clean the setInterval or get the value of the setCount function itself to return the previous value as the current value, to be able to work without the famous re-render, right?

No, :D only this code already works.

We added a console.log to check how many times this component was rendered during the count update, we will check how many times it runs in the console:

Image description

Magic!!!! In solid your code does not run more than once unless it is required at some point in the code.

But how does Solid work?

Solid's data management is built around a set of flexible reactive primitives that are responsible for all updates. It has a very similar approach to MobX or Vue, except it never trades its granularity for a VDOM. Dependencies are automatically tracked when you access their reactive values ​​in your effects and JSX View code, Solid primitives come in the form of create calls that usually return tuples, where usually the first element is a readable primitive and the second is a setter. It is common to refer only to the human-readable part by the primitive name.

Primitives

Solid is composed of 3 primary primitives: Signal, Memo and Effect. At its core is the Observer pattern, where Signals (and Memos) are tracked involving Memos and Effects.

Signals are the simplest primitives. They contain get and set value and functions so that we can intercept when they are read and written.

const [count, setCount] = createSignal(0);
Enter fullscreen mode Exit fullscreen mode

Effects are functions that involve readings from our Signal and are executed again whenever the value of a dependent Signal changes. This is useful for creating side effects such as rendering.

createEffect(() => console.log("The latest count is", count()));
Enter fullscreen mode Exit fullscreen mode

Finally, Memos are cached derived values. They share the properties of Signals and Effects. They track their own dependent Signals, rerunning only when they change, and are themselves traceable Signals.

const fullName = createMemo(() => `${firstName()} ${lastName()}`);
Enter fullscreen mode Exit fullscreen mode

How does this Signal work?

Signals are event emitters that contain a list of signatures. They notify their subscribers whenever their value changes.

Things get more interesting as these subscriptions happen. Solid uses automatic dependency tracking. Updates happen automatically as data changes.

The trick is a global stack at runtime. Before an Effect or Memo executes (or re-executes) its developer-provided function, it pushes itself onto that stack. Then any Signal that is read checks if there is a current listener on the stack, and if so, adds the listener to its subscriptions.

You can think like this:

function createSignal(value) {
  const subscribers = new Set();

  const read = () => {
    const listener = getCurrentListener();
    if (listener) subscribers.add(listener);
    return value;
  };

  const write = (nextValue) => {
    value = nextValue;
    for (const sub of subscribers) sub.run();
  };

  return [read, write];
}
Enter fullscreen mode Exit fullscreen mode

Link Github SolidJs: SolidJS

Top comments (0)