Stores with createStore
Managing nested objects and arrays reactively with createStore, and why signals alone become clumsy for structured state.
読了時間 2 分
createSignal works well for independent, standalone values. Once your state is a nested object or array — a user profile with an address, a todo list with per-item flags — updating it through signals means replacing the entire object on every change, which gets clumsy fast. createStore is Solid's answer to structured state.
The problem createStore solves
const [user, setUser] = createSignal({
name: "Ana",
address: { city: "Lisbon", zip: "1000" },
});
// Updating one nested field means rebuilding everything around it
setUser({
...user(),
address: { ...user().address, city: "Porto" },
});This works, but it only gets more awkward as nesting grows, and every update recreates the entire object tree even though only one field actually changed.
Creating a store
import { createStore } from "solid-js/store";
const [user, setUser] = createStore({
name: "Ana",
address: { city: "Lisbon", zip: "1000" },
});
console.log(user.name); // "Ana" — read directly, no function callTwo things stand out immediately. First, createStore comes from solid-js/store, a separate module from core solid-js. Second, you read store values as plain property access — user.name, not user().name — because a store wraps its data in a proxy that tracks property access automatically; each property behaves like its own fine-grained signal.
Updating a store
setUser("address", "city", "Porto");setStore accepts a path into the object followed by the new value — here, "go into address, then city, and set it to Porto." This updates only that one property; anything in the UI reading user.name or user.address.zip isn't touched, and anything reading user.address.city updates precisely. This is the structured-data equivalent of a signal's fine-grained update, extended to nested paths instead of a single value.
function Profile() {
const [user, setUser] = createStore({
name: "Ana",
address: { city: "Lisbon", zip: "1000" },
});
return (
<div>
<p>{user.name} lives in {user.address.city}</p>
<button onClick={() => setUser("address", "city", "Porto")}>
Move to Porto
</button>
</div>
);
}Updating with a function
setStore also accepts a function for the final value, receiving the current value at that path — useful for updates that depend on the existing state, same idea as a signal's functional setter:
const [todos, setTodos] = createStore([
{ id: 1, text: "Learn Solid", done: false },
]);
setTodos(0, "done", (done) => !done);Stores vs signals: which to use
Reach for createStore when your state is naturally nested — objects with sub-objects, arrays of objects you'll update individually. Reach for createSignal for standalone values that don't decompose further — a count, a boolean flag, a selected tab. The two are frequently used side by side in the same component; there's no rule that a component must pick only one.