Discover A New Framework: SolidJS 🧑‍💻
Tags: solidjs, javascript, vite
Published: 2022-09-02 11:18:09

> _The world of Web development is constantly evolving, and new JavaScript libraries are appearing (always more interesting), day after day..._

This year (once again), I challenged myself to experiment with a new technology. As the Web is constantly evolving, it's important to get up to date every 6 months. Following the last "[State Of JS](https://2021.stateofjs.com)" survey's results, I found two new frameworks that seemed promising to me: [AlpineJS](https://alpinejs.dev) x [SolidJS](https://www.solidjs.com)

_**NB**: I'm enthusiast, I thirst for knowledge, and recently I wanted to learn something fresh, like the discovery of  [Svelte](https://svelte.dev) in 2019._

After reading two or three documentations, and analyzing several dozen lines of code, I preferred to choose **SolidJS**, especially because of the JSX omnipresence, as well as concepts similar to my everyday tool, namely: [React](https://reactjs.org)!

In this post, I'll obviously talk about what SolidJS is, but especially about how I understood this new framework in a learning process (from theory to practice). I wouldn't fail to mention the dependencies that revolve around its ecosystem. Finally, we will see if this last one keeps its promise of "_fine-grained reactivity_", while being simple and efficient.

# WTF is SolidJS!?

_**NB**: Before talking about the main topic, a short explanation... As part of the work on setting up a peer-to-peer network in JavaScript (upcoming post...), I wanted to have an interface / an application to control actions related to a node (other than Postman). Rather than developing this from already known basics, I started to learn more in detail what SolidJS really is, and to implement it in a large-scale project. Here is my learning process!_

Like many JavaScript libraries before it, SolidJS is a component-oriented framework for building Web interfaces. Although close to React in some of its concepts (and its syntax), it wants to be lighter and more reactive than this last one, like Svelte. Indeed, SolidJS advocates "_fine-grained reactivity_" by making changes directly from the DOM, rather than through a virtual DOM.

As a result (and according to [performance tests](https://bit.ly/3PwrDOV) of the open-source community), this new tool would be both faster than React, Vue, Preact (even if it has a lighter virtual DOM than React), as well as Svelte, and would be the first in terms of speed and memory allocation!

{% gist https://gist.github.com/DmnChzl/cc03faf8468e1a946634375d91dbeade %}

{% gist https://gist.github.com/DmnChzl/b121a9cd8dc73e7100a7f56b17d6c78d %}

## Get started with SolidJS

Getting started with SolidJS is child's play (I still recommend having the basics of JavaScript, as well as the mastery of some  component-oriented framework notions, to fully understand what reactivity is...). By using the **Rich Harris**'s library (`degit`), we can scaffold the project. Therefore, the JavaScript environment will be pre-configured to work from a bundler, ~~not Webpack this time~~, but [Vite](https://vitejs.dev)!

Powered mainly by the Vue community, Vite is a new kind of JavaScript tool, to start a new Web project very quickly, while offering a modern environment. Inspired by [RollupJS](https://rollupjs.org) (but also [ESBuild](https://esbuild.github.io)), Vite has many features, including:

- NPM dependency management
- Pre-bundle of sources (saving a lot of time at startup)
- **H**ot **M**odule **R**eplacement (**HMR**)
- Native TypeScript support
- On-the-fly JSX transpilation
- Support for CSS modules
- Dynamic import management
- Build optimization, etc.

_**NB**: Vite ecosystem also brings [Vitest](https://vitest.dev) for unit testing, as an alternative to Jest._

SolidJS offers a wide range of templates to initialize a new project, with the choice of language: JavaScript or TypeScript; the integration of a ready-to-use UI library (including the great [TailwindCSS](https://tailwindcss.com)), but also the implementation of an unit test engine: [Jest](https://jestjs.io) / Vitest (optional).

```jsx
import { render } from 'solid-js/web';
import { createSignal, onMount, onCleanup } from 'solid-js';

const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  const interval = setInterval(() => {
    setCount(count => count + 1);
  }, 1000);

  onMount(() => console.log('Mounted'));
  onCleanup(() => clearInterval(interval));

  return <div>Count value is {count()}</div>;
};

render(() => <CountingComponent />, document.getElementById('root'));
```

<figcaption>Counter component with SolidJS</figcaption>

```jsx
import { render } from 'react-dom';
import { useState, useEffect } from 'react';

const CountingComponent = () => {
  const [count, setCount] = useState(0);
  const interval = setInterval(() => {
    setCount(count => count + 1);
  }, 1000);

  useEffect(() => {
    console.log('Mounted');
    return cleanup = () => clearInterval(interval);
  }, []);

  return <div>Count value is {count}</div>;
};

render(() => <CountingComponent />, document.getElementById('root'));
```

<figcaption>Counter component with React</figcaption>

Above, here's what a first single counter component might look like with SolidJS, and its opposite on the React side. We quickly notice the similarities between these two libraries. And yet, SolidJS seems to be more explicit and simple!

Indeed, rather than having a `useEffect` hook to handle the mounting and unmounting behaviors of the component (through a `cleanup` function), SolidJS exposes its actions with functions of its life cycle: `onMount` and `onCleanup`.

Similarly, the `createSignal` API is relatively identical to React's `useState` hook in how its works, since the data flow is unidirectional. On one side the accessor (immutable), and on the other the "_setter_". However, there is a significant difference with SolidJS! The "_fine-grained reactivity_" concept is based on the principle that components only renders once, and that functions associated with the **Signals** are executed as soon as they're updated. Changes will then be impacted directly in the DOM. Since the access to the value of a Signal is only possible by calling it (presence of parentheses), I consider the accessor more like a "_getter_", rather than a constant... **Signals** are the key concept of SolidJS!

Overall SolidJS is really easy to take in hand, learning its API is also easier due to rich documentation and a "_playground_" section to understand each aspect of this library, and to gain in skill step by step.

Although basic, SolidJS is a complete framework, since it also offers a routing solution (`npm install solid-app-router`), a "_code splitting_" feature through asynchronous component loading (made possible by the bundler), or even the ability to render your application server-side (SSR). So SolidJS, ranks among the big ones (React, Vue, etc.) as a Web library to create user interfaces.

# Building user interfaces

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/df22886udgdj5fa0yon2.png)

Enough theory, now let's move on to practice! And what better way to put it into practice than to evolve in a concrete case… To do this, I suggest you to create a new project, to handle the feeding and display of a modal, in order to approach the reactivity notions of SolidJS. Let's go!

```
npx degit solidjs/templates/js-vitest my-awesome-app
cd my-awesome-app
npm install
```

Et voilà! You should see a new directory appear (`my-awesome-app` in my case), lite in terms of dependencies, but already pre-configured to work `/vit/`!

_**NB**: As part of my work, I also added TailwindCSS support to project initialization (`npx degit solidjs/templates/js-tailwindcss`), then merged both Vite configurations manually._

```jsx
import { mergeProps, Switch, Match, Show } from 'solid-js';
import { IconInfo, IconSuccess, IconWarning, IconError } from './icons';

export const Modal = (props) => {
  const merged = mergeProps({
    type: 'INFO',
    title: 'Info',
    onOk: () => console.log('Ok')
  }, props);

  return (
    <div class="container">
      <div class="modal__background" />

      <div class="modal">
        <span class="modal__icon">
          <Switch fallback={<IconInfo class="text-blue-700 bg-blue-100" />}>
            <Match when={merged.type === 'SUCCESS'}>
              <IconSuccess class="text-green-700 bg-green-100" />
            </Match>

            <Match when={merged.type === 'WARNING'}>
              <IconWarning class="text-orange-700 bg-orange-100" />
            </Match>

            <Match when={merged.type === 'ERROR'}>
              <IconError class="text-red-700 bg-red-100" />
            </Match>
          </Switch>
        <span>

        <span class="modal__title">{merged.title}</span>

        {props.children}

        <div class="modal__actions">
          <Show when={typeof props.onCancel === 'function'}>
            <button class="modal__action-cancel" type="button" onClick={props.onCancel}>
              Cancel
            </button>
          </Show>

          <button class="modal__action-ok" type="button" onClick={merged.onOk}>
            Ok
          </button>
        </div>
      </div>
    </div>
  );
}
```

<figcaption>Modal.jsx</figcaption>

The modal represents a _stateless_ component that visually extends across the screen (considering the dark background and centering of the main element), and which only waits for `props` values to come to life. This also simplify unit testing with Vitest (and **Testing Library**).

The SolidJS API can make the syntax a bit verbose at first "_Vue_" 😉, but its components are essential to effect a change in the DOM, because of a virtual DOM lack. So its simplicity lies in a clever combination of **Signals** (immutable) and components (`<Show>`, `<Switch>`, `<For>`, etc.) in order to make the DOM reactive to the right places!

_**NB**: Note that here, we use the `mergeProps` API to impose default `props` values. Again, it's important to use this function to conserve reactivity, rather than initializing these same values directly from the component's signature (like with React)._

```jsx
import { render } from 'solid-js/web';
import { createSignal, onMount, Show } from 'solid-js';
import { Modal } from './Modal';

export const App = () => {
  const [showModal, setShowModal] = createSignal(false);
  const [modalType, setModalType] = createSignal('');
  const [modalTitle, setModalTitle] = createSignal('Lorem ipsum');

  onMount(() => {
    if (showModal()) {
      setTimeout(() => {
        setModalType('SUCCESS');
        setModalTitle('Everything is good');
      }, 1500);

      setTimeout(() => {
        setModalType('ERROR');
        setModalTitle('Something is wrong');
      }, 3000);
    }
  });

  return (
    <div class="app">
      <Show when={showModal()}>
        <Modal
          type={modalType()}
          title={modalTitle()}
          onOk={() => {
            setModalType('WARNING');
            setShowModal(false);
          }}>
          <p>Lorem ipsum dolor sit amet</p>
        </Modal>
      </Show>

      <button style={{ margin: 'auto' }} onClick={() => setShowModal(currentVal => !currentVal)}>
        {showModal() ? 'Hide Modal' : 'Show Modal'}
      </button>
    </div>
  );
};

render(() => <App />, document.getElementById('app'));
```

<figcaption>main.jsx</figcaption>

In terms of usage, the triggering of the modal's display (as well as its feeding) takes place via **Signals**, from the parent component. Above, I had fun changing the modal's appearance over time, to observe the reactivity. One more time, it may seem verbose, but it's simple and effective!

This mode of operation is sometimes not enough, especially when developing an application at scale, or when using a routing system (`npm install solid-app-router`), to split the pages of its application. Handling the modal should be possible by using a "_props drilling_" logic (parent to child, child to sub-child, etc.), but we should quickly fall into a complexity linked to component nesting, as well as their maintenance... No problem! As for modals, there is the `<Portal>` component; for everything else, there is the "**Context**" API!

```jsx
import { mergeProps, Switch, Match, Show, Portal } from 'solid-js';
import { IconInfo, IconSuccess, IconWarning, IconError } from './icons';

export const Modal = props => (
  <Portal mount={document.getElementById('modal')}>
    <ModalComponent {...props} />
  </Portal>
);

// Move <Modal /> To <ModalComponent />
export const ModalComponent = props => {
  const merged = mergeProps(
    {
      type: 'INFO',
      title: 'Info',
      onOk: () => console.log('Ok')
    },
    props
  );

  return <div class="container">{/* ... */}</div>;
};
```

<figcaption>Modal.jsx</figcaption>

## Context API

_**NB**: In [a previous post](https://dev.to/dmnchzl/diy-state-management-react-vs-vue-4nja), I explain how it's possible to implement a "homemade" **Redux**, with React's Context API, here the logic is the same..._

```jsx
import { createContext, createEffect, createSignal, useContext } from 'solid-js';
import { createStore } from 'solid-js/store';

const store = createContext();

const { Provider } = store;

export const useStore = () => {
  return useContext(store);
};

const StateProvider = props => {
  const [currentIdx, setCurrentIdx] = createSignal(0);
  const [state, setState] = createStore({
    email: 'morty.smith@pm.me'
  });

  createEffect(() => {
    console.log('currentIdx', currentIdx());
  });

  const value = [
    {
      currentIdx,
      fieldValues: state
    },
    {
      incrementIndex() {
        setCurrentIdx(prevCurrentIdx => prevCurrentIdx + 1);
      },
      decrementIndex() {
        setCurrentIdx(prevCurrentIdx => prevCurrentIdx - 1);
      },
      getField(key) {
        return state[key] || '';
      },
      setField(key, value) {
        setState({ [key]: value });
      }
    }
  ];

  return <Provider value={value}>{props.children}</Provider>;
};

export default StateProvider;
```

<figcaption>StateProvider.jsx</figcaption>

```jsx
import { Form, InputField } from './components';
import { useStore } from './StateProvider';

const StateConsumer = () => {
  const [{ currentIdx }, { getField, setField }] = useStore();

  return (
    <Form index={currentIdx()}>
      <InputField
        label="Email"
        type="email"
        placeholder="rick.sanchez@pm.me"
        defaultValue={getField('email')}
        handleChange={e => setField('email', e.target.value)}
      />
    </Form>
  );
};
```

<figcaption>StateConsumer.jsx</figcaption>

State Management (via the Context API), is an ideal reactive programming paradigm for large developments, avoiding the problem of "_props drilling_", but also the presence of double / triple data... In this way, it's said that the "_store_" is the only source of truth of the application.

_**NB**: To learn more about this topic, don't hesitate to consult the source code of my project on [Gitlab](https://gitlab.com/dmnchzl/diy-context) and [Vercel](https://diy-context.vercel.app)._

## Would you like some extra unit tests?

The ecosystem powered by SolidJS, advocates modern technologies such as [PNPM](https://pnpm.io), Vite, Vitest but also [Testing Library](https://testing-library.com). Based on the implementation of Preact, the SolidJS team brings the component-oriented unit testing framework, which has already proven itself with React, Vue, Angular, Cypress, etc.

```jsx
import { render, fireEvent } from 'solid-testing-library';
import { describe, it, expect, vi } from 'vitest';
import { Modal } from './Modal';

describe('<Modal />', () => {
  it('Should Render', () => {
    const { getByText, unmount } = render(() => (
      <Modal title="> Modal Title Goes Here <">
        <p>Lorem ipsum dolor sit amet</p>
      </Modal>
    ));

    expect(getByText('> Modal Title Goes Here <')).toBeInTheDocument();
    expect(getByText('Lorem ipsum dolor sit amet')).toBeInTheDocument();
    unmount();
  });

  it('Should Trigger onClick Event(s)', async () => {
    const mockedOnCancel = vi.fn();
    const mockedOnOk = vi.fn();

    const { getByText, unmount } = render(() => (
      <Modal type="WARNING" title="Warning" onCancel={mockedOnCancel} onOk={mockedOnOk} />
    ));

    const cancelBtn = getByText('Cancel');
    fireEvent.click(cancelBtn);
    expect(mockedOnCancel).toHaveBeenCalled();

    const okBtn = getByText('Ok');
    fireEvent.click(okBtn);
    expect(mockedOnOk).toHaveBeenCalled();

    unmount();
  });
});
```

<figcaption>Modal.test.jsx</figcaption>

# Simplicity + Reactivity === Performance

I really enjoyed playing with this new framework (as often with JavaScript technologies). There are many notions of its counterparts, as well as new ones such as **Signals**, or the `createResource` function (very useful for querying APIs). I didn't have great difficulty creating my node control application (from scratch), since this last one offers great flexibility in project organization. So, the Developer eXperience is optimal!

_**NB**: Unlike React (or Vue), SolidJS compiles its code during the development period (like Svelte). In this way, it's able to effectively and quickly effect changes in the "real" DOM; one bonus point in favor of DX!_ 👍

This first overview makes me want to know more and propose the approach in professional situation. Perhaps replacing Preact, or even React... SolidJS doesn't offer only components and features demonstrated above. If I had to make a quick list, this library also offers:

- Dynamic rendering of an element list, from (not `Array.prototype.map` but) its `<For>` component
- Encapsulation of asynchronous calls (as resources), for better visibility of the loading state, as well as the presence (or not) of error(s)
- Interface display in case of lazy loading (components or resources), via the `<Suspense>` component
- Display of interfaces in case of error(s), to avoid application crash
- The SSR operating mode, for performance gains, but also for better SEO (here SolidJS, directly confronts [Nuxt](https://v3.nuxtjs.org), [Next](https://nextjs.org) or [SvelteKit](https://kit.svelte.dev))
- Or, the "_store_" reconciliation, allowing **Redux** to coexist in a scaled application

Due to its easy-to-understand concepts, and despite the absence of a virtual DOM, SolidJS has proven its strength and stability (in 5 years of development), pushing reactivity in its purest form: **Signals**! Just like Svelte, this future-oriented framework has bright days ahead of it. This is my favorite of the year! ❤️