SolidJS
Trying out SolidJS
- Solid is a web framework competing in the same battleground as React, Vue, and Svelte. One might say that Solid is like React + Svelte in the sense that it uses JSX with React-like API with a compilation-based approach like Svelte.
- I was attracted to Solid primarily because of its proposition of small + fast with SSR and SSG support via SolidStart. So far I’m having a great authoring experience.
- Transitioning from React to Solid was straightforward at first but eventually I had to confront the differences. Going from React to Preact was a matter of translating a few minor API differences, but going from React to Solid requires respecting the differences in reactivity.
- I still miss the ergonomics of Recoil where you get a global view of all atoms so you can easily set state during SSR.
- In general I aim for 0.2-0.3 seconds for LCP and TTI (100 on Google Lighthouse is table stakes for all but the most complicated apps). I’ll be finished with a non-trivial toy app and report back on performance.
Reactivity in React and Solid
All major framework approaches to frontend reactivity allow you to build diagrams between state primitives and their derived or computed values. The idea is that when you update state then all subscribed components will be updated.
| Library | Reactive Primitive | Derived Value |
|---|---|---|
| React | Hooks | Memo |
| Solid | Store/Signals | Memo |
| Recoil | Atom | Selector |
| Redux | Store | Selector |
In Solid, a store is a collection of signals in the form of an object or array.
Signals allow you to Read-Your-Writes
This React snippet is a common example of a beginner mistake as React cannot show you fresh values until re-running your component function.
function ReactExample() {
const [count, setCount] = useState(0)
useEffect(() => {
const elem = document.getElementById("count")
console.log(`count: ${count}. dom: ${elem.textContent}`)
setCount(x => x + 1)
console.log(`state: ${count}. dom: ${elem.textContent}`)
})
return <div id="count">{count}</div>
}
In Solid createSignal will return a two element array of an accessor and a
setter function, where the accessor can pull the latest value of the signal.
function SolidExample() {
const [count, setCount] = createSignal(0)
createEffect(() => {
const elem = document.getElementById("count")
console.log(`count: ${count()}. dom: ${elem.textContent}`)
setCount(x => x + 1)
console.log(`count: ${count()}. dom: ${elem.textContent}`)
})
return <div id="count">{count()}</div>
}
Solid’s reactivity is granular
Solid attempts to determine at compile-time which elements in your JSX tree are
dependent on which signals, and it will leave behind a minimal runtime for
granular mutation of your DOM when your state updates. In the example below,
the elements containing time, mouse.x, and mouse.y can be updated
independently from each other.
function SolidExample() {
const [time, setTime] = createSignal(0)
const [mouse, setMouse] = createStore({ x: 0, y: 0 })
console.log("I run only once since I don't depend on any signal.")
console.log("Updated time: ", time())
onMount(() => {
const id = setInterval(() => setTime(x => x + 1), 1000)
onCleanup(() => clearInterval(id))
})
onMount(() => {
const handler = e => setMouse({ x: e.clientX, y: e.clientY })
document.addEventListener("mousemove", handler)
onCleanup(() => document.removeEventListener("mousemove", handler))
})
return (
<section>
<div>Time: {time()}</div>
<div>Mouse:
<ul>
<li>x: {mouse.x}</li>
<li>y: {mouse.y}</li>
</ul>
</div>
</section>
)
}
In React, the entire <section> must be re-rendered in order to reflect changes
to either time or mouse.
Note that I don’t have to call an accessor function to access the value of a the
mousestore, but that’s only a syntactical convenience as Solid intercepts the object access and calls a getter function for you.