SolidJS has one of the most elegant reactive APIs in the JavaScript ecosystem. Fine-grained reactivity without a virtual DOM — and SolidStart adds full-stack capabilities.
Reactive Primitives
Solid's core reactivity API:
import { createSignal, createEffect, createMemo } from "solid-js"
// Signals — reactive state
const [count, setCount] = createSignal(0)
const [user, setUser] = createSignal({ name: "Alice", score: 0 })
// Derived values — only recompute when dependencies change
const doubled = createMemo(() => count() * 2)
const greeting = createMemo(() => `Hello, ${user().name}!`)
// Effects — run when dependencies change
createEffect(() => {
console.log(`Count is now: ${count()}`)
// Automatically tracks count() as dependency
})
// Batch updates
import { batch } from "solid-js"
batch(() => {
setCount(10)
setUser({ name: "Bob", score: 100 })
})
Stores — Deep Reactive Objects
import { createStore, produce } from "solid-js/store"
const [state, setState] = createStore({
users: [
{ id: 1, name: "Alice", tasks: ["Code", "Review"] },
{ id: 2, name: "Bob", tasks: ["Design"] }
],
filter: "all"
})
// Granular updates — only affected DOM nodes update
setState("users", 0, "name", "Alice Updated")
setState("filter", "active")
// Immer-like produce
setState(produce((s) => {
s.users.push({ id: 3, name: "Charlie", tasks: [] })
s.users[0].tasks.push("Deploy")
}))
Resources — Async Data Fetching
import { createResource, Suspense } from "solid-js"
const [userId, setUserId] = createSignal("1")
const [user, { refetch, mutate }] = createResource(
userId, // Source signal
async (id) => {
const res = await fetch(`/api/users/${id}`)
return res.json()
}
)
// In JSX
function UserProfile() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Show when={user()} fallback={<p>No user</p>}>
<h1>{user().name}</h1>
<p>{user().email}</p>
</Show>
</Suspense>
)
}
// Optimistic update
mutate(prev => ({ ...prev, name: "Updated" }))
refetch()
SolidStart — Full-Stack API
Server functions and API routes:
// src/routes/api/posts.js
import { json } from "@solidjs/router"
export async function GET() {
const posts = await db.post.findMany()
return json(posts)
}
export async function POST({ request }) {
const body = await request.json()
const post = await db.post.create({ data: body })
return json(post, { status: 201 })
}
Server Functions (RPC)
"use server"
import { db } from "~/lib/db"
export async function getUser(id) {
return db.user.findUnique({ where: { id } })
}
export async function createPost(title, content) {
return db.post.create({ data: { title, content } })
}
import { getUser, createPost } from "~/server/actions"
function Dashboard() {
const [user] = createResource(() => getUser("123"))
const handleCreate = async () => {
await createPost("New Post", "Content here")
}
return <button onClick={handleCreate}>Create Post</button>
}
Context API
import { createContext, useContext } from "solid-js"
const ThemeContext = createContext("light")
function ThemeProvider(props) {
const [theme, setTheme] = createSignal("light")
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{props.children}
</ThemeContext.Provider>
)
}
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<button
class={theme() === "dark" ? "btn-dark" : "btn-light"}
onClick={() => setTheme(t => t === "dark" ? "light" : "dark")}
>
Toggle Theme
</button>
)
}
Key Takeaways
- Signals for fine-grained reactive state
- Stores for deep reactive objects with granular updates
- Resources for async data with Suspense
- createMemo/createEffect for derived values and side effects
- SolidStart for full-stack with server functions
- No virtual DOM — direct DOM updates for peak performance
Explore Solid docs for the complete API.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)