A Solid choice


Last year I’ve crossed the 10 year developer milestone, a whole decade. I’ve started my journey with UnityScript, it used to be the language used by Unity to develop games. I wanted to create my own games, but I found I was lacking the basics of UnityScript and that was JavaScript, than one thing led to another and BAM! 10 years later here I am a web developer.

Through out these 10 years, the most consistent technology in my life has been React. After miserably failing at learning Angular at age 16, learning React was that light bulb turning on moment for me, web development finally made sense, and I’ve been using it consistently both professionally and personally ever since. Later TypeScript would have the same effect on my life.

Every so often I would try and learn different languages, their phylosophies and libraries: I tried Go, Rust, GDScript… And every time I came back to web development I kept encountering the same problem. React had a very unique way to do things, and your only option is to work with it. I would try other alternatives but none felt like a good replacement.

And so more, and more I found myself falling for the “React-way”, only using libraries with a react affixed or prefixed to them, or with a NextJS integration. Now is this a skill issue on my part? Perhaps, but I still wanted to try a different way, if not only to know what I could be missing.

Initially the choices were between Svelte and Solid, purely because they had some of the best performance among UI libraries. But after checking both out, I decided to use Solid for being familiar to React but improving on many of my perceived problems.

What’s the same?

  • Components are Typed Functions: You build your UI from functions that return JSX. The idiomatic way to type them is with the Component type from solid-js.
  • Props are Typed: You can define props for your components just as you would in React, typically using an interface or type alias.

import type { Component } from 'solid-js';

// Define the shape of your props
type GreetingProps = {
  name: string;
};

// Use the Component<T> type for your component
const Greeting: Component<GreetingProps> = (props) => {
  return <div>Hello, {props.name}!</div>;
};

const App: Component = () => {
  return <Greeting name="Solid Developer" />;
};

What feels familiar?

State: useStatecreateSignal

In Solid, you use createSignal. It’s generic, so it can infer the type from its initial value, or you can provide it explicitly. A signal is a tuple containing an Accessor (the getter) and a Setter.


import { createSignal } from 'solid-js';
import type { Accessor, Setter } from 'solid-js';

// Type is inferred as 'number'
const [count, setCount] = createSignal(0);
// count is type: Accessor<number>
// setCount is type: Setter<number>

// You can also be explicit, which is useful for complex types
const [user, setUser] = createSignal<User | null>(null);

The most critical difference for React developers is that the state variable (count) is an accessor function, not a static value.


// To read the value, you must call the function:
<h1>The count is: {count()}</h1>

// This is wrong and will not be reactive:
<h1>The count is: {count}</h1>

Why? Calling count() is how you tell Solid’s reactive system that this part of your UI depends on the signal. It’s how the magic happens without a VDOM.

Side Effects: useEffectcreateEffect

createEffect automatically tracks any signals you read inside it. No dependency array needed!


import { createEffect, createSignal } from 'solid-js';

const [count, setCount] = createSignal(0);

// This effect automatically re-runs whenever 'count()' changes
createEffect(() => {
  console.log('The count is now', count());
});

Memoization: useMemocreateMemo

For deriving reactive values, use createMemo. It also tracks its own dependencies and returns a read-only Accessor.


import { createMemo, createSignal } from 'solid-js';
import type { Accessor } from 'solid-js';

const [count, setCount] = createSignal(5);

// The type of doubleCount is Accessor<number>
const doubleCount: Accessor<number> = createMemo(() => count() * 2);

// Use it in your JSX just like a signal
<p>Double the count is: {doubleCount()}</p>

What’s different?

In Solid, your component functions run only once to set up the view. They don’t re-run. Instead, Solid uses a reactive system that surgically updates the exact parts of the DOM that need to change. This fundamental difference eliminates the need for dependency arrays and a whole class of bugs related to stale closures.

But because your components only run once, you cannot use array methods like .map() directly in your JSX for reactive data. To handle this, Solid provides special, typed helper components.

  • For lists, use the <For> component.
  • For conditional rendering, use the <Show> component.

These components are reactive and ensure the DOM is updated efficiently.


import { createSignal, For, Show } from 'solid-js';
import type { Component } from 'solid-js';

interface User {
  id: number;
  name: string;
}

const [users, setUsers] = createSignal<User[]>([
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
]);

const [currentUser, setCurrentUser] = createSignal<User>(users()[0]);

// Correct way to render a list
// 'user' is correctly typed as 'User' inside the loop
<For each={users()}>
  {(user, i) => <div>{i() + 1}: {user.name}</div>}
</For>

// Correct way to show/hide content
// The 'when' prop narrows the type of 'user()' inside the block
<Show when={currentUser()} fallback={<p>No user selected.</p>}>
  {user => <p>Current user is: {user.name}</p>}
</Show>

Learn More