DEV Community

Cover image for Why you will love Solid JS as a React developer
TuanNQ
TuanNQ

Posted on

Why you will love Solid JS as a React developer

Yet another JS framework? Why should I care about that Solid JS thing? Isn't that React is good enough? Here's why you should care about Solid JS from a viewpoint of a React developer.

Have you ever encounter a massive React component that a team spends months to build happened to be awfully laggy? It seems that nobody can figure out it's re-rendering so much and if one figure out, it would be too risky to re-write the logic.

Huge React component run too slow

Have you ever integrate a non-React library into React? Somehow your component just freeze because it was re-rendered again and again, and your React state not in sync with that outside library at all?

React please work nicely with Vanilla JS library

If I tell you to use another JS framework to solve those problem, you'd tell me, wouldn't I have to throw away all my muscle memory and technical details I know about React to learn another new framework? How can I justify that?

Here Solid come to the rescue! Solid has nearly identical syntax compare to React and really, really fast. You and your team can get productive with Solid in almost no time and you'll never have to worry about performance issues again!

React and Solid code so similar to each other

Another amazing thing is that contrary to React, Solid also allow you to integrate with non-React library almost seamlessly.

In short, Solid allows you to have the best from both world: the JSX component way of doing things combine with the power of good old Vanilla JS. Plus, you never have to worry about all the useRef, memo,... things for performance anymore.

In this article we'll learn about how Solid JS can make our lives significantly easier, get familiar with basic Solid JS (which trust me, nearly identical with React, almost no learning curve required!), and how to do the store thing (like Redux) and context in Solid JS.

First, let's delve into the pain points of React that Solid solve.

Pain points of React

Excessive re-rendering problem (eg: resizing, drag'n'drop, very large form...)

Have you ever tried to write a resizable component yourself in React? If I tell you to write a resizable React component that wrap around another expensive React component, what would you do?

Here's how you might approach the problem: change the state, and React will handle the UI change for you. With that in mind, you might write some code like this:

Simple resizing React idea

import React, { useState, useRef } from 'react';

export const ResizableComponent: React.FC = () => {
    const [width, setWidth] = useState<number>(100);

    const containerRef = useRef<HTMLDivElement>(null);

    const onDrag = (e: MouseEvent) => {
        const delta =
          e.pageX - containerRef.current!.getBoundingClientRect().right;
                  
        setWidth((prevWidth) => prevWidth + delta);
    };

    return (
        <div ref={containerRef} style={{ width }}>

          <ExpensiveComponent />
          
          {/* Handle */}
          <div
            onMouseDown={() => {
              document.addEventListener("mousemove", onDrag);

              document.addEventListener("mouseup", () => {
                document.removeEventListener("mousemove", onDrag);
              });
            }}
          />
        </div>
      );
    }
Enter fullscreen mode Exit fullscreen mode

But oh boy was it laggy! The reason is that the Expensive Component was re-rendered so many times. To avoid this, you need to store the width in ref to avoid the excessive re-rendering.

Unspoken rule: you should put everything in state, except those that changed 1000 times per second.

The same pain point apply for drag and drop, or re-render the whole gigantic form on every key strokes,... You have to use ref and deal with the integration between state and ref, and its various edge cases and bugs.

Integrate with not-for-React library

Have you ever find some very nice library but it's for Vanilla Javascript instead of for React? (for example: D3, GSAP, VisJS Timeline,...)

If those library don't have a React port, and you're just get started with React, good luck try to integrate those. All those crazy re-rendering and useRef, useEffect, useMemo, useCallback,... none of that works! Somehow my page just lag, my library just got re-render again and again, and my state absolutely not in sync with those outside library.

Integrate some simple outside library sometimes requires quite a bit of knowledge about React!

If you wish those nasty issues are just magically dealt with, and you don't even have to learn yet another crazy JS framework, Solid JS come to the rescue!

Why you should try Solid JS?

Syntax extremely familiar with React

Don't have time to learn a new framework? Have trouble hiring or training people some obscure JS framework?

Don't worry! Just replace useState with createSignal, useEffect with createEffect, and voila!, you're starting to get productive with Solid JS!

export const App = () => {
  const [count, setCount] = createSignal<number>(1);

  return (
    <div>
      <div>{count()}</div>
      <button
        onClick={() => {
          setCount((prevState) => prevState + 1);
        }}
      >
        Increment
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

And remember don't destructuring props like this:

// NOT OK!
const Component = ({ data, onClick }) => {}

// OK!
const Component = (props) => {
    props.data;
    props.onClick();
}
Enter fullscreen mode Exit fullscreen mode

For a majority of the time, that's enough!

Of course there are some more minor differences, but just consult the documentation, no big deal!

Don't worry about excessive re-rendering

Let's write the resizable component above again but in Solid JS:

import { createSignal, type VoidComponent } from "solid-js";

const App: VoidComponent = () => {
  const [width, setWidth] = createSignal<number>(100);

  let containerRef!: HTMLDivElement;

  const onDrag = (e: MouseEvent) => {
    if (containerRef) {
      const delta = e.pageX - containerRef.getBoundingClientRect().right;
      setWidth((prevWidth) => prevWidth + delta);
    }
  };

  return (
    <div
      ref={containerRef}
      style={{
        width: `${width()}px`,
      }}
    >
      <ExpensiveComponent />

      <div
        onMouseDown={() => {
          document.addEventListener("mousemove", onDrag);
          
          document.addEventListener("mouseup", () => {
            document.removeEventListener("mousemove", onDrag);
          });
        }}
      />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

If you try it, it's super smooth! The main reason is that ExpensiveComponent is not forced to re-render again and again.

Solid JS only update what's needed to be updated, and don't touch anything else. They call it "fine-grained reactivity", I guess.

With Solid JS, you can truly just put everything in state without worrying about performance and all the crazy optimization like ref, useCallback, useMemo ever again!

Integrate nicely with Vanilla Javascript library

Need some nice timeline library (vis-timeline) which currently don't have a React nor Solid port?

Here you go, just make a ref and wrap all the Vanilla Javascript code on onMount (equivalent of useEffect with an empty array dependency):

import { onMount, type VoidComponent } from "solid-js";
import { type DataItem, Timeline } from "vis-timeline";
import "vis-timeline/dist/vis-timeline-graph2d.min.css";

const App: VoidComponent = () => {
  let containerRef!: HTMLDivElement;

  onMount(() => {
    // Items
    const items = [
        { id: 1, content: "item 1", start: "2014-04-20" },
        { id: 2, content: "item 2", start: "2014-04-14" },
        { id: 3, content: "item 3", start: "2014-04-18" },
        { id: 4, content: "item 4", start: "2014-04-16", end: "2014-04-19" },
        { id: 5, content: "item 5", start: "2014-04-25" },
        { id: 6, content: "item 6", start: "2014-04-27", type: "point" },
    ];

    // Create a Timeline
    const timeline = new Timeline(containerRef, items, {});

    // Do whatever you need with timeline here
  });

  return (
    <div
      ref={containerRef}
      style={{
        width: `600px`,
        height: `400px`,
      }}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Here's the result:

Vis Timeline screen capture

Need some fancy D3js?

import { onMount, type VoidComponent } from "solid-js";
import * as d3 from "d3";

const D3Demo: VoidComponent = () => {
  let container!: HTMLDivElement;

  onMount(() => {
    // Declare the chart dimensions and margins.
    const width = 640;
    const height = 400;
    const marginTop = 20;
    const marginRight = 20;
    const marginBottom = 30;
    const marginLeft = 40;

    // Declare the x (horizontal position) scale.
    const x = d3
      .scaleUtc()
      .domain([new Date("2023-01-01"), new Date("2024-01-01")])
      .range([marginLeft, width - marginRight]);

    // Declare the y (vertical position) scale.
    const y = d3
      .scaleLinear()
      .domain([0, 100])
      .range([height - marginBottom, marginTop]);

    // Create the SVG container.
    const svg = d3.create("svg").attr("width", width).attr("height", height);

    // Add the x-axis.
    svg
      .append("g")
      .attr("transform", `translate(0,${height - marginBottom})`)
      .call(d3.axisBottom(x));
      
    // Add the y-axis.
    svg
      .append("g")
      .attr("transform", `translate(${marginLeft},0)`)
      .call(d3.axisLeft(y));

    // Append the SVG element.
    container.append(svg.node()!);
  });

  return <div ref={container} />;
};
Enter fullscreen mode Exit fullscreen mode

Here's the result:

D3js screen capture

All I need to do is to make a container ref, then copy and paste from the documentation to onMount! And it works! They said the reason you can do this because each component only mount once.

Solid JS give you both options to write in a normal Vanilla Javascript way and nicely integrate with the React way. Imagine how cool is that! You have the best from both worlds!

But what about store management and context?

Ok, I was sold on Solid JS, you said, but what about all the store management Redux thing? What's the equivalent of Context and Redux in Solid JS world?

Did I tell you that you can move state outside of component is Solid JS?

const [count, setCount] = createSignal<number>(1);

export const App = () => {
  return (
    <div>
      <div>{count()}</div>
      <button
        onClick={() => {
          setCount((prevState) => prevState + 1);
        }}
      >
        Increment
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Let's talk about what this means to the store management problem below.

Context

If you need some state that both component A and component B need to use, just move it outside of A and B:

// State (outside of component A and B)
const [count, setCount] = createSignal<number>(1);

// Component A
export const ComponentA = () => {
  return (
    <div>
      <div>{count()}</div>
      <button
        onClick={() => {
          setCount((prevState) => prevState + 1);
        }}
      >
        Increment
      </button>
    </div>
  );
};

// Component B
export const ComponentB = () => {
  return (
    <div>
      <div>{count()}</div>
      <button
        onClick={() => {
          setCount((prevState) => prevState - 1);
        }}
      >
        Decrement
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

No need to worry about props drilling, lift up state, performance issues (and the god forbid useImperativeHandle!) anymore.

Redux

In case of Redux, Solid JS offer createStore for more complex data structure.

import { createSignal, For, onMount, type VoidComponent } from "solid-js";
import { createStore, produce } from "solid-js/store";

// Imagine this is a slice
const [todos, setTodos] = createStore<string[]>([]);

// Imagine this is selector
export const selectTodos = () => {
  return todos;
};

// Imagine this is a dispatcher
export const addTodo = (newTodo: string) => {
  setTodos(produce((state) => state.push(newTodo)));
};

// Add todo component
const AddTodo: VoidComponent = () => {
  const [todo, setTodo] = createSignal<string>("");
  
  return (
    <>
      <input
        onInput={(e) => {
          setTodo(e.currentTarget.value);
        }}
      />

      <button
        onClick={() => {
          addTodo(todo());
        }}
      >
        Add todo
      </button>
    </>
  );
};

// List todo component
const TodoList: VoidComponent = () => {
  const todos = selectTodos();

  return <For each={todos}>{(todo) => <div>{todo}</div>}</For>;
};
Enter fullscreen mode Exit fullscreen mode

And that's how you can get the familiar Redux-y way in Solid JS just like you get with React.

But what are the downsides?

Since Solid JS has not yet reach popularity like React, its ecosystem still lack many of the equivalent libraries like react-flow, react-three-fiber, react-input-number (for input validation and masking),...

Solid JS does have Solid Start, which is like NextJS, but obviously is not battle-tested like NextJS yet.

Conclusion

First time trying out Solid JS, I feel like having magic 'It just works TM' experience. Whatever you throwing at Solid, it will always works, and works fast! I can focus on building my application, instead of learning and fighting the framework, and worrying about performance.

With almost no new learning required, ease of coding couple with insane performance, plus easily combine the best from both world (Vanilla JS way and React way of doing things) in one codebase, I feel like Solid JS is the perfect version of React. I think Solid JS has a bright future ahead.

If you have a personal or a small project which need to be fast-moving, performance, and familiar with the React people, definitely trying Solid JS!

Credits

If you like the cute fish that I'm using, check out: https://thenounproject.com/browse/collection-icon/stripe-emotions-106667/.

Cute fish

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)