I'm developing a new project in SolidJS, and one aspect that's particularly confusing is that setStore functions can be called in infinite possible ways.
I found the docs quite confusing.
Since Grok 3 was released yesterday, and I found it to be really good, I asked it to analyze the source code and test specs and create a cheatsheet.
It wasn't as simple as that - all in all, it needed about 6-7 back-and-forth exchanges until I was satisfied. Here is the full conversation if you're interested: link
I'll share what it gave me, as I think it's the best cheatsheet for SolidJS setState/setStore yet. It's a very interesting use case that we can now take full source code and test specs and ask an LLM to generate a cheatsheet from them.
Note: setStore and setState are the same thing - you can name these as you want.
If you find any mistakes, please comment below the post.
SolidJS setStore
Cheatsheet
setStore
is the setter function returned by createStore
in SolidJS, used to update a reactive store. It supports versatile patterns for updating objects, arrays, and nested structures. Updates are batched automatically via batch
for efficient reactivity, and the store remains immutable (direct mutations like state.a = 1
are blocked with a dev-mode warning).
General Syntax: setStore(...args: any[]): void
1. Top-Level Updates
Update the entire store or its top-level structure.
a. Replace Array or Merge Object
Syntax:
setStore(newValue)
Description:
Arrays: Replaces the entire array with
newValue
.Objects: Merges
newValue
into the top-level store, updating specified properties.
Examples:
// Array Replacement
const [state, setState] = createStore([1, 2, 3]);
setState([4, 5, 6]); // state becomes [4, 5, 6]
// Object Merge
const [state, setState] = createStore({ a: 1, b: 2 });
setState({ b: 3 }); // state becomes { a: 1, b: 3 }
Source:
./src/store.ts
-updateArray
(arrays),updatePath
→mergeStoreNode
(objects).Test:
./test/store.spec.ts
- "Test Array", "Top level merge".Notes: Use
reconcile
for full object replacement.
b. Update Using a Function
Syntax:
setStore((prevState, traversed) => newValue)
Description: Computes the new top-level value based on the current state.
traversed
is[]
.Example:
const [state, setState] = createStore({ a: 1, b: 2 });
setState(s => ({ ...s, b: s.a + 2 })); // state becomes { a: 1, b: 3 }
Source:
./src/store.ts
-updatePath
applies the function.Test:
./test/store.spec.ts
- "Top level state function merge".Notes: Useful for dynamic updates.
2. Property Path Updates (Objects)
Update specific properties or nested properties by specifying a path.
a. Update a Single Property
Syntax:
setStore(key, value)
Description: Sets a top-level property to
value
.Example:
const [state, setState] = createStore({ a: 1, b: 2 });
setState("a", 3); // state becomes { a: 3, b: 2 }
Source:
./src/store.ts
-updatePath
→setProperty
.Test:
./test/store.spec.ts
- "Simple Key Value".Notes: Works with any writable key (string, number, symbol).
b. Update a Nested Property
Syntax:
setStore(key1, key2, ..., keyN, value | partialObject | (prev, traversed) => newValue)
Description: Updates a nested property or merges an object at the path. Supports:
Direct value assignment.
Object merging.
Function updates (
traversed
is the path, e.g.,["key1", "key2"]
).
Examples:
const [state, setState] = createStore({ data: { a: 1, b: 2 } });
setState("data", "a", 3); // { data: { a: 3, b: 2 } }
setState("data", { b: 3 }); // { data: { a: 1, b: 3 } }
setState("data", "a", v => v + 1); // { data: { a: 2, b: 2 } }
Source:
./src/store.ts
-updatePath
traverses and updates.Test:
./test/store.spec.ts
- "Nested merge", "Nested state function merge".Notes: Typed up to 7 levels; deeper paths use a fallback. Use
undefined
to delete.
3. Array Updates
Update elements in arrays (top-level or nested) by index, range, or filter, including appending.
a. Update a Specific Index (Including Append)
Syntax:
setStore(index, value)
Description: Updates the element at
index
. Ifindex
equals the current length, appends; if beyond, creates a sparse array.Examples:
const [state, setState] = createStore([1, 2, 3]);
setState(1, 4); // state becomes [1, 4, 3]
setState(state.length, 5); // state becomes [1, 2, 3, 5] (append)
setState(5, 6); // state becomes [1, 2, 3, undefined, undefined, 6] (sparse)
Source:
./src/store.ts
-updatePath
→setProperty
.Test:
./test/store.spec.ts
- "Test Array" (implicitly supports).Notes: Appending is a special case of index update.
b. Update Multiple Specific Indices (Object Syntax)
Syntax:
setStore({ index1: value1, index2: value2, ... })
Description: Updates multiple indices using an object with index keys.
Example:
const [state, setState] = createStore([1, 2, 3, 4, 5]);
setState({ 1: 4, 3: 8 }); // state becomes [1, 4, 3, 8, 5]
Source:
./src/store.ts
-updatePath
→mergeStoreNode
.Test:
./test/store.spec.ts
- "Update Specific Object".Notes: Sparse updates supported.
c. Update Specific Indices (Array Syntax)
Syntax:
setStore([index1, index2, ...], value | (item, traversed) => newValue)
Description: Updates specific indices with a value or function.
Examples:
const [state, setState] = createStore([1, 2, 3, 4, 5]);
setState([1, 3], 10); // state becomes [1, 10, 3, 10, 5]
setState([1, 3], r => r * 2); // state becomes [1, 4, 3, 8, 5]
Source:
./src/store.ts
-updatePath
iterates over indices.Test:
./test/store.spec.ts
- "Update Specific".Notes:
traversed
includes indices in function case.
d. Update by Filter Function
Syntax:
setStore(filterFn, value | (item, traversed) => newValue)
Description: Updates elements where
filterFn(item, index)
returnstrue
.Example:
const [state, setState] = createStore([1, 2, 3, 4, 5]);
setState((item, i) => i % 2 === 1, r => r * 2); // state becomes [1, 4, 3, 8, 5]
Source:
./src/store.ts
-updatePath
applies filter.Test:
./test/store.spec.ts
- "Update filterFn".Notes: Efficient for conditional updates.
e. Update by Range
Syntax:
setStore({ from?: number, to?: number, by?: number }, value | (item, traversed) => newValue)
Description: Updates elements in a range with optional step (
by
). Defaults:from: 0
,to: length - 1
,by: 1
.Examples:
const [state, setState] = createStore([1, 2, 3, 4, 5]);
setState({ from: 1, to: 3, by: 2 }, r => r * 2); // state becomes [1, 4, 3, 8, 5]
setState({}, r => r * 2); // state becomes [2, 4, 6, 8, 10]
Source:
./src/store.ts
-updatePath
iterates range.Test:
./test/store.spec.ts
- "Update traversal range".Notes: Flexible for stepping through arrays.
f. Nested Array Updates
Syntax:
setStore(key1, key2, ..., indexOrFilterOrRange, value | (item, traversed) => newValue)
Description: Applies array updates (index, filter, range, append) to a nested array.
Examples:
const [state, setState] = createStore({ list: [1, 2, 3] });
setState("list", 1, 4); // { list: [1, 4, 3] }
setState("list", state.list.length, 5); // { list: [1, 2, 3, 5] } (append)
setState("list", i => i % 2 === 0, r => r * 2); // { list: [2, 2, 6] }
Source:
./src/store.ts
-updatePath
combines path and array logic.Test:
./test/store.spec.ts
- "Test Array Nested".Notes: Supports all array update methods.
4. Using Modifiers
Advanced update patterns via ./src/modifiers.ts
.
a. Reconcile
Syntax:
setStore(reconcile(newValue, { key?: string | null, merge?: boolean }))
Description: Reconciles differences between current state and
newValue
.key
: Matches objects by a key (e.g.,"id"
) for arrays.merge
: Merges instead of replacing whentrue
.
Examples:
const [state, setState] = createStore({ data: 2, missing: "soon" });
setState(reconcile({ data: 5 })); // { data: 5 }
const [state, setState] = createStore({ users: [{ id: 1, name: "John" }] });
setState("users", reconcile([{ id: 1, name: "Jake" }], { key: "id" })); // { users: [{ id: 1, name: "Jake" }] }
Source:
./src/modifiers.ts
-reconcile
→applyState
.Test:
./test/modifiers.spec.ts
- "Reconcile a simple object", "Reconcile reorder a keyed array".Notes: Efficient for syncing with external data.
b. Produce
Syntax:
setStore(produce(draft => { ... }))
Description: Mutates a draft (Immer-style), applying changes reactively (e.g.,
push
to append).Examples:
const [state, setState] = createStore({ data: { a: 1 } });
setState(produce(s => { s.data.a = 2 })); // { data: { a: 2 } }
const [state, setState] = createStore({ todos: [{ id: 1, done: false }] });
setState(produce(s => { s.todos.push({ id: 2, done: true }) })); // { todos: [{ id: 1, done: false }, { id: 2, done: true }] }
Source:
./src/modifiers.ts
-produce
uses a proxy.Test:
./test/modifiers.spec.ts
- "Test Array Mutation".Notes: Simplifies complex updates, including appending.
Detailed Examples for Appending with setStore
1. Index-Based Appending
Syntax: setStore(index, value)
Description: Sets a value at the specified index. If
index
equals the current array length, it appends the value by extending the array. Ifindex
exceeds the length, it creates a sparse array withundefined
gaps.Source:
./src/store.ts
-updatePath
→setProperty
:
setProperty(current, part, value); // Extends array if part >= length
Example 1.1: Append to Top-Level Array
import { createStore } from "solid-js/store";
const [state, setState] = createStore([1, 2, 3]);
console.log(state); // Initial: [1, 2, 3]
setState(state.length, 4); // Append 4 at index 3 (length is 3)
console.log(state); // After: [1, 2, 3, 4]
Explanation:
Initial length is 3, so
state.length
is 3.setState(3, 4)
sets the value at index 3, extending the array to[1, 2, 3, 4]
.
Use Case: Simple single-item append to a flat array.
Validation: Supported by
setProperty
behavior; no explicit test, but implicit in array index updates.
Example 1.2: Append to Nested Array
import { createStore } from "solid-js/store";
const [state, setState] = createStore({ items: ["a", "b"] });
console.log(state.items); // Initial: ["a", "b"]
setState("items", state.items.length, "c"); // Append "c" at index 2
console.log(state.items); // After: ["a", "b", "c"]
Explanation:
state.items.length
is 2.setState("items", 2, "c")
navigates to the"items"
array and appends"c"
at index 2.
Use Case: Adding an item to a nested list (e.g., a todo list within an object).
Validation: Consistent with
./test/store.spec.ts
- "Test Array Nested" logic.
2. Array Replacement
Syntax: setStore([...state, newValue])
(top-level) or setStore("key", [...state.key, newValue])
(nested)
Description: Replaces the entire array with a new one that includes the original elements plus appended values, using the spread operator.
Source:
./src/store.ts
-updateArray
:
if (Array.isArray(next)) {
let i = 0, len = next.length;
for (; i < len; i++) { setProperty(current, i, next[i]); }
setProperty(current, "length", len);
}
Example 2.1: Append to Top-Level Array
import { createStore } from "solid-js/store";
const [state, setState] = createStore([10, 20]);
console.log(state); // Initial: [10, 20]
setState([...state, 30]); // Replace with new array including 30
console.log(state); // After: [10, 20, 30]
Explanation:
[...state, 30]
creates[10, 20, 30]
.updateArray
replaces the store with this new array.
Use Case: Appending one or more items in a single update (e.g., batch addition).
Test:
./test/store.spec.ts
- "Test Array":
setTodos([...todos, { id: 3, title: "Go Home", done: false }]);
Example 2.2: Append Multiple Values to Top-Level Array
import { createStore } from "solid-js/store";
const [state, setState] = createStore(["x", "y"]);
console.log(state); // Initial: ["x", "y"]
setState([...state, "z", "w"]); // Append "z" and "w"
console.log(state); // After: ["x", "y", "z", "w"]
Explanation:
[...state, "z", "w"]
constructs["x", "y", "z", "w"]
.Entire array is replaced with the new one.
Use Case: Bulk appending (e.g., adding multiple log entries).
Example 2.3: Append to Nested Array
import { createStore } from "solid-js/store";
const [state, setState] = createStore({ log: ["info", "warn"] });
console.log(state.log); // Initial: ["info", "warn"]
setState("log", [...state.log, "error"]); // Append "error" to "log"
console.log(state.log); // After: ["info", "warn", "error"]
Explanation:
updatePath
navigates to"log"
.[...state.log, "error"]
creates["info", "warn", "error"]
, which replaces the nested array.
Use Case: Appending to a log or history array within an object.
Test:
./test/store.spec.ts
- "Test Array Nested":
setState("todos", [...state.todos, { id: 3, title: "Go Home", done: false }]);
3. Using produce
with push
Syntax: setStore(produce(draft => { draft.push(newValue) }))
(top-level) or setStore(produce(draft => { draft.key.push(newValue) }))
(nested)
Description: Uses
produce
to provide a mutable draft of the state, allowing array methods likepush
to append values reactively.Source:
./src/modifiers.ts
-produce
:
let proxy = producers.get(state) || new Proxy(state, setterTraps);
fn(proxy);
setterTraps.set
handles array mutations likepush
.
Example 3.1: Append to Top-Level Array
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore([100, 200]);
console.log(state); // Initial: [100, 200]
setState(produce(s => {
s.push(300);
})); // Append 300
console.log(state); // After: [100, 200, 300]
Explanation:
produce
creates a proxy of the array.s.push(300)
extends the array, andsetterTraps
applies the change.
Use Case: Appending with additional mutations in one go.
Test:
./test/modifiers.spec.ts
- "Test Top-Level Array Mutation":
s.push({ id: 3, title: "Go Home", done: false });
Example 3.2: Append Multiple Values with Other Mutations
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore([{ id: 1, active: true }]);
console.log(state); // Initial: [{ id: 1, active: true }]
setState(produce(s => {
s[0].active = false; // Mutate existing item
s.push({ id: 2, active: true }); // Append new item
s.push({ id: 3, active: false });
}));
console.log(state); // After: [{ id: 1, active: false }, { id: 2, active: true }, { id: 3, active: false }]
Explanation:
produce
allows multiple operations: updating an existing element and appending two new ones.Changes are applied reactively in a single batch.
Use Case: Complex updates (e.g., updating and adding todos).
Example 3.3: Append to Nested Array
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore({ queue: ["task1", "task2"] });
console.log(state.queue); // Initial: ["task1", "task2"]
setState(produce(s => {
s.queue.push("task3");
})); // Append "task3" to "queue"
console.log(state.queue); // After: ["task1", "task2", "task3"]
Explanation:
produce
proxies the entire state.s.queue.push("task3")
appends to the nested array.
Use Case: Adding items to a nested queue or playlist.
Test:
./test/modifiers.spec.ts
- "Test Array Mutation":
s.todos.push({ id: 3, title: "Go Home", done: false });
Additional Notes
Type Safety:
SetStoreFunction<T>
ensures type correctness up to 7 levels; deeper paths use a fallback.Immutability: Direct mutations are blocked (
./src/store.ts
-proxyTraps.set
).Source Validation: All patterns align with
updatePath
,updateArray
,reconcile
, andproduce
implementations.Test Coverage: Confirmed by
./test/store.spec.ts
and./test/modifiers.spec.ts
.
This is the full, corrected cheatsheet reflecting all setStore
capabilities, including appending. Let me know if you need further refinements or examples!
+ TODO:
add an item at the beginning of an array:
const [state, setState] = createStore([1, 2, 3]);
setState([newItem, ...state]); // Add at the beginning
setStore("items", prevItems => [newItem, ...prevItems]);
const [state, setState] = createStore([1, 2, 3]);
setState(produce(draft => {
draft.unshift(newItem); // Add to the beginning using unshift
}));
---
Remove from Beginning:
setStore("items", prevItems => prevItems.slice(1));
setState(
produce((draft) => {
draft.items.shift(); // Use shift to remove from the beginning
})
);
---
Remove from Middle:
setStore("items", prevItems => prevItems.filter(item => item !== targetItem));
setState("items", items => items.filter(item => item.active));