How To Manage Your State In SolidJS

How To Manage Your State In SolidJS

Signal vs. Store vs. Mutable and when to use what

State management is the part of frontend development where a lot of complexity lies. Good state management can make the difference in how easy it is to maintain software for a long time. In this blog post, I will explain how you can manage your state in SolidJS and the differences between the utils SolidJS gives us to manage our state.

We will look at the differences between a Signal, a Store, and a Mutable and try to understand when we should use one of them.

createSignal

A Signal is a piece of data that is observable by SolidJS. The data can be an object, array, a simple boolean, string or integer. To create a Signal we use the ES6 destructuring assignment syntax, so we can put the values from an array into variables.

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

const Example = () => {
    const [show, setShow] = createSignal(false);
    return <div>{show() ? <h1>show is true</h1> : <h1>show is false</h1>}</div>;
};

render(() => <Example />, document.getElementById("app") as HTMLElement);

This looks similar to the React useState Hook. The difference is, in SolidJS you call your state via a function.

show()

If we call the getter function, SolidJS will add it automatically to the subscription list of the created Signal. When the data of your Signal changes, SolidJS will always know where to update the UI and notifies all subscribers from the Signal.

If our Signal is an object with multiple properties like this

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

const Example = () => {    
    const [obj, setObj] = createSignal({
        show:   true,
        name:   "my name"
     });

    return <div>{obj().show ? <h1>show is true</h1> : <h1>show is false</h1>}</div>;
};

render(() => <Example />, document.getElementById("app") as   HTMLElement);

SolidJS will see it as one Signal. That means that even if the name property of the object changes and it is never called via the getter function, SolidJS will call all subscribers to update the UI. This can cause unwanted rerenders of the UI.

To handle this use case SolidJS has Stores.

createStore

A Store in SolidJS wraps the properties of your state object into single Signals for each property. So each of your properties gets its own subscriber list.

import { render } from "solid-js/web";
import { createStore } from "solid-js/store";

const Example = () => {
    const [obj, setObj] = createStore({
        show:   true,
        name:   "my name"
    });

    return <div>{obj.show ? <h1>show is true</h1> : <h1>show is false</h1>}</div>;
};

render(() => <Example />, document.getElementById("app") as   HTMLElement);

With createStore, the UI will only rerender when the "show" property changes. Since every property of the state object has its own subscriber list, no rerender will happen if we change the "name" property.

Note that if we use a Store we don't have to call the data with a function, we can call our data as an property.

SolidJS comes with a neat path pattern syntax to change data in a Store. You can read more about the Syntax here or in the official SolidJS docs or in this blog post.

import { render } from "solid-js/web";
import { createStore } from "solid-js/store";

const Example = () => {
    const [obj, setObj] = createStore({
        show:   true,
        name:   "my name"
    });
    return <div>{obj.show ? <h1>show is true</h1> : <h1>show is false</h1>}</div>;
};

render(() => <Example />, document.getElementById("app") as   HTMLElement);

In the following code example, we have a button that changes the name property in the Store. If we use a store we need to change our data with a setter, which in our case is called setObj.

import {render} from "solid-js/web";
import {createStore} from "solid-js/store";

const Example = () => {
    const [obj, setObj] = createStore({
        show: true,
        name: "my name"
    });

    return (
        <div>
            <button onClick={() => setObj({name: 'new name'})}>click me</button>
            {obj.show ? <h1>show is true</h1> : <h1>show is false</h1>}
        </div>
    );
};

render(() => <Example />, document.getElementById("app") as HTMLElement);

Since we used a Store instead of a Signal, our UI will not execute a rerender when the button is clicked. No rerender will take place because the property name is not used. If we want our UI to rerender we have to change the property that is used, which in our case is the boolean 'show'.

import {render} from "solid-js/web";
import {createStore} from "solid-js/store";

const Example = () => {
    const [obj, setObj] = createStore({
        show: true,
        name: "my name"
    });

    return (
        <div>
            <button onClick={() => setObj({show: !obj.show})}>click me</button>
            {obj.show ? <h1>show is true</h1> : <h1>show is false</h1>}
        </div>
    );
};

render(() => <Example />, document.getElementById("app") as HTMLElement);

Now with clicking on the button, the 'show' property of our Store is changed and the UI gets rerendered. Stores help us to avoid unnecessary and expensive rerender when we don't need them.

If you want to integrate some library like Mobx, SolidJS comes with a solution for you.

createMutable

Note: The SolidJS documentation recommends using createStore instead of createMutable.
You can read more in the official SolidJS docs.

With createMutable we create a Store that can be overwritten. This basically means we can do what we want with the data from our createMutable and SolidJS will take care of the changes. We can pass it around and change the data where we want.

This sounds great but makes it hard to predict what happens in your application and can lead to unclean and hard maintainable code.

import {render} from "solid-js/web";
import {createMutable} from "solid-js/store";

const Example = () => {
    const state = createMutable({
        show: true,
        name: "my name"
    });

    return (
        <div>
            <button onClick={() => state.name = 'NEW NAME'}>click me</button>
            {state.show ? <h1>show is true</h1> : <h1>show is false</h1>}
        </div>
    );
};

render(() => <Example />, document.getElementById("app") as HTMLElement);

We declare our state variable with the createMutable function. Because we are using createMutable we can change the data without a setter unlike when using a Store. We can read our data like we did when we used createStore. Since createMutable also returns a Store object, the rendering behaves like a state change with useStore. When we click on the button in the code example the UI is not rerendering because the property 'name' is not used.

The following code will cause the UI to update.

import {render} from "solid-js/web";
import {createMutable} from "solid-js/store";

const Example = () => {
    const state = createMutable({
        show: true,
        name: "my name"
    });

    return (
        <div>
            <button onClick={() => state.show = !state.show}>click me</button>
            {state.show ? <h1>show is true</h1> : <h1>show is false</h1>}
        </div>
    );
};

render(() => <Example />, document.getElementById("app") as HTMLElement);

With createMutable you can also use setter and getter. You can read more about them in the official docs.

When to use what

Signal

If you have one specific value that you want to make reactive or you don't need nested reactivity you should use createSignal.

Store

If you have more than one value that you want to make reactive and you need nested reactivity or you want to have more control over the updating of the UI you should use createStore.

Mutable

If you want to pass your state data into code that is unaware of the SolidJS reactivity you should use createMutable.

Note from the official SoloidJS documentation:

A mutable state can be passed around and mutated anywhere, which can make it harder to follow and easier to break unidirectional flow. It is generally recommended to use createStore instead. The produce modifier can give many of the same benefits without any of the downsides.

Did you find this article valuable?

Support Karim Ould Mahieddine by becoming a sponsor. Any amount is appreciated!