Skip to content

Solid vs React

Posted on:March 4, 2023

So lately there has been a lot of talk of signals on twitter. I don’t have much experience with them so I decided to try rewriting my React Color Guesser app in Solid and see what all the syntax differences are.

First let me explain a bit about Solid. Solid is founded on fine-grained reactivity. It doesn’t have a virtual dom and does dom updates in place as things change making it more performant than alternatives. Another thing to note is it uses jsx so that part should be familiar if you’re coming from React.

I am going to focus primarily on syntax in this article but if you want additional reading check these posts by Ryan Carniato out.

The Evolution of Signals in JavaScript
React vs Signals: 10 Years Later
Making the Case for Signals in JavaScript

Side-by-side comparison

React

import React, { useState } from 'react';
import { generateColors, getCorrectColor } from './colors';
import Game from './Game';

const NUM_COLORS = 3;

function App() {
  const [game, setGame] = useState(0);

  const colors = generateColors(NUM_COLORS);
  const correctColor = getCorrectColor(colors);

  const nextGame = () => {
    setGame(game + 1);
  }

  return (
    <Game
      key={game}
      nextGame={nextGame}
      colors={colors}
      correctColor={correctColor}
    />
  )
}

export default App

Solid

import Game from "./Game"

function App() {
  return (
    <Game />
  )
}

export default App

So your initial thought might be that Solid requires less code, but this isn’t actually the case. I was able to move the state within the Game component in Solid.

In React I wanted the state to be in the App component so I could reset it using the key prop when going to the next game.

An interesting mind shift had to occur in that I can’t rely on a rerender as Solid components only get ran once.

Let’s look at some more code:

React

import React, { useState } from 'react';

type GameProps = {
    nextGame: () => void;
    colors: string[];
    correctColor: string;
}

function Game({ nextGame, colors, correctColor }: GameProps) {
    const [guess, setGuess] = useState<string | undefined>(undefined);

    const wonGame = guess === correctColor;

    return (
        <div
            style={{
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                margin: 'auto',
            }}
        >
            <h1>Color Codes</h1>
            <h2>{correctColor}</h2>
            <h2>What color is this?</h2>

            <div data-testid="color-container" style={{ display: 'flex', gap: 10 }}>
                {colors.map(color => (
                    <button
                        key={color}
                        onClick={() => {
                            setGuess(color);
                        }}
                        style={{
                            height: 100,
                            width: 100,
                            cursor: 'pointer',
                            background: color
                        }}
                        data-testid={color === correctColor ? "correct-color" : "incorrect-color"}>
                    </button>
                ))}
            </div>

            {guess ?
                wonGame ? <div style={{ textAlign: 'center' }}>
                    <p>Correct!</p>
                    <p><button onClick={nextGame}>Play Again</button></p>
                </div>
                    : <p>Incorrect!</p>
                : null
            }
        </div>
    )
}

export default Game

Solid

import { createSignal, For, Show } from "solid-js";
import { generateColors, getCorrectColor } from "./colors";

const NUM_COLORS = 3;

function Game() {
    const [colors, setColors] = createSignal(generateColors(NUM_COLORS));
    const [correctColor, setCorrectColor] = createSignal(getCorrectColor(colors()));
    const [guess, setGuess] = createSignal<string | undefined>(undefined);

    const nextGame = () => {
        setColors(generateColors(NUM_COLORS));
        setCorrectColor(getCorrectColor(colors()));
        setGuess('');
    }

    const wonGame = () => guess() === correctColor();

    return (
        <div
            style={{
                display: "flex",
                'flex-direction': "column",
                'align-items': "center",
                margin: 'auto',
            }}
        >
            <h1>Color Codes</h1>
            <h2>{correctColor}</h2>
            <h2>What color is this?</h2>

            <div data-testid="color-container" style={{ display: 'flex', gap: "10px" }}>
                <For each={colors()}>{color =>
                    <button
                        onClick={() => {
                            setGuess(color);
                        }}
                        style={{
                            height: "100px",
                            width: "100px",
                            cursor: 'pointer',
                            background: color
                        }}
                        data-testid={color === correctColor() ? "correct-color" : "incorrect-color"}>
                    </button>
                }</For>
            </div>

            <Show when={guess()}>
                <Show when={wonGame()} fallback={<p>Incorrect!</p>}>
                    <div style={{ 'text-align': 'center' }}>
                        <p>Correct!</p>
                        <p><button onClick={nextGame}>Play Again</button></p>
                    </div>
                </Show>
            </Show>
        </div >
    )
}

export default Game;

As I mentioned earlier I had to use more of createSignal than useState. This is so I can change the colors when a new game is created. I can no longer rely on the key prop and rerenders to do this for me.

The other interesting thing to note is you have to use the values as function calls. For example colors() instead of just colors. This allows Solid to maintain its reactivity. You also need to wrap derived state in a function so it can stay reactive based on the values.

const wonGame = () => guess() === correctColor();

The other slight difference is css properties are not camel cased like react. For example flex-direction instead of flexDirection.

Also Solid has control flow components you use instead of map and ternaries. You can see in my example how I use <Show> and <For>. I am not totally sure how I feel about this. In some ways I think it makes things a bit more readable when things are nested, but it also doesn’t feel just like JavaScript in the way that React does.

Conclusion

This has been a brief tour into some of the things I learned rewriting this app. As mentioned I wanted to get a feel of how signals work in practicality. Honestly the ergonomics are familiar to useState but the one interesting thing is you can use them outside components unlike hooks! 🤯

const [colors, setColors] = createSignal(generateColors(NUM_COLORS));
const [correctColor, setCorrectColor] = createSignal(getCorrectColor(colors()));

function Game() {
    const [guess, setGuess] = createSignal<string | undefined>(undefined);
}

I can see this being useful for global state as you can also just export it which is much simpler than using React Context.