Skip to content

Commit 80ade5f

Browse files
committed
integrate usage guides into single Usage section
1 parent 9c57410 commit 80ade5f

File tree

4 files changed

+323
-391
lines changed

4 files changed

+323
-391
lines changed

www/src/content/docs/store.mdx

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
---
2+
title: 💾 Simple store
3+
description: A reactive store that combines the simplicity of signals with the power of "selectors" you'd find in Zustand or Redux.
4+
sidebar:
5+
label: Get started
6+
order: 1
7+
---
8+
9+
import { LinkCard, Tabs, TabItem } from '@astrojs/starlight/components';
10+
11+
<LinkCard href="https://github.com/bholmesdev/simplestack-store" title="Source code" />
12+
13+
A reactive store that combines the simplicity of signals with the power of "selectors" you'd find in Zustand or Redux.
14+
15+
```tsx
16+
import { store } from "@simplestack/store";
17+
18+
const documentStore = store({
19+
title: "Untitled",
20+
description: "Description",
21+
});
22+
23+
const title = documentStore.select("title");
24+
const description = documentStore.select("description");
25+
26+
title.set("New title");
27+
console.log(title.get()); // "New title"
28+
description.set("New description");
29+
console.log(description.get()); // "New description"
30+
```
31+
32+
## Installation
33+
34+
Install the dependency from npm:
35+
36+
```bash
37+
npm i @simplestack/store
38+
```
39+
40+
Then, import the store and use it in your component:
41+
42+
```tsx
43+
import { store } from "@simplestack/store";
44+
```
45+
46+
## Usage
47+
48+
### 1. Create a store
49+
50+
You can create a store using the `store()` function, passing an initial value as an argument.
51+
52+
We suggest creating stores outside of components so they aren't recreated on each render:
53+
54+
```ts
55+
import { store } from "@simplestack/store";
56+
57+
export const counterStore = store(0);
58+
```
59+
60+
### 2. Set the value of a store
61+
62+
You can set the value of a store by calling the `set()` method. This accepts both a value and a function that returns the new value:
63+
64+
```tsx
65+
counterStore.set(1);
66+
counterStore.set((n) => n + 1);
67+
```
68+
69+
This can be called both from within a component and from outside of a component. This allows you to create utility functions that operate on a store:
70+
71+
```tsx ins={5-7}
72+
import { store } from "@simplestack/store";
73+
74+
export const counterStore = store(0);
75+
76+
export function incrementCounter() {
77+
counterStore.set((n) => n + 1);
78+
}
79+
```
80+
81+
82+
### 3. Use the store in a component
83+
84+
You can subscribe to the value fo a store from your React components using the `useStoreValue` hook. This accepts the store as an argument and returns the current value of the store.
85+
86+
<Tabs syncKey="framework">
87+
<TabItem label="Vite">
88+
```tsx "useStoreValue"
89+
// src/components/Counter.tsx
90+
import { useStoreValue } from "@simplestack/store/react";
91+
import { counterStore } from "../stores/counter";
92+
93+
export function Counter() {
94+
const count = useStoreValue(counterStore);
95+
96+
return (
97+
<button onClick={() => counterStore.set((n) => n + 1)}>
98+
Count: {count}
99+
</button>
100+
);
101+
}
102+
```
103+
</TabItem>
104+
<TabItem label="Next.js">
105+
106+
:::note
107+
Any component using `useStoreValue` must be a `"use client"` component.
108+
:::
109+
110+
```tsx "useStoreValue"
111+
// app/components/Counter.tsx
112+
"use client";
113+
114+
import { useStoreValue } from "@simplestack/store/react";
115+
import { counterStore } from "@/lib/counter";
116+
117+
export default function Counter() {
118+
const count = useStoreValue(counterStore);
119+
120+
return (
121+
<button onClick={() => counterStore.set((n) => n + 1)}>
122+
Count: {count}
123+
</button>
124+
);
125+
}
126+
```
127+
</TabItem>
128+
</Tabs>
129+
130+
### 4. Create sub-stores for fine-grained updates
131+
132+
As your store grows more complex, you may want to operate on specific parts of the store.
133+
134+
In this example, say we have a store to track a user's preferences, including their theme. Naively, you can operate on nested values by calling `.set()` and reconstructing the nested object using spread syntax, like so:
135+
136+
```tsx
137+
const userStore = store({
138+
name: "Guest",
139+
preferences: { theme: "dark" },
140+
});
141+
142+
function setTheme(theme: string) {
143+
userStore.set((state) => ({
144+
...state,
145+
preferences: { ...state.preferences, theme },
146+
}));
147+
}
148+
```
149+
150+
However, this is fairly verbose and error-prone. Instead, you can create "sub-stores" by calling `select('key')` on the parent store, where `key` is the object key or array index you want to select. This creates a new store instance that lets you operate on the selected object key.
151+
152+
In this example, we can create a sub-store for the theme preference:
153+
154+
```tsx ins={6}
155+
const userStore = store({
156+
name: "Guest",
157+
preferences: { theme: "dark" },
158+
});
159+
160+
const themeStore = userStore.select("preferences").select("theme");
161+
```
162+
163+
Then, we can update the user's theme preference by calling `set()` on the sub-store directly:
164+
165+
```tsx ins={6} del={2-5}
166+
function setTheme(theme: string) {
167+
userStore.set((state) => ({
168+
...state,
169+
preferences: { ...state.preferences, theme },
170+
}));
171+
themeStore.set(theme);
172+
}
173+
```
174+
175+
Changes to `themeStore` automatically update `userStore`, and vice versa.
176+
177+
You can then subscribe to a sub-store the same way you subscribe to a parent store. Pass the sub-store to the `useStoreValue` hook:
178+
179+
<Tabs syncKey="framework">
180+
<TabItem label="Vite">
181+
```tsx ins={6}
182+
// src/components/ThemeToggle.tsx
183+
import { useStoreValue } from "@simplestack/store/react";
184+
import { themeStore } from "../stores/user";
185+
186+
export function ThemeToggle() {
187+
const theme = useStoreValue(themeStore);
188+
return (
189+
<button onClick={() => themeStore.set(theme === "dark" ? "light" : "dark")}>
190+
Theme: {theme}
191+
</button>
192+
);
193+
}
194+
```
195+
</TabItem>
196+
<TabItem label="Next.js">
197+
```tsx ins={8}
198+
// app/components/ThemeToggle.tsx
199+
"use client";
200+
201+
import { useStoreValue } from "@simplestack/store/react";
202+
import { themeStore } from "@/lib/user";
203+
204+
export default function ThemeToggle() {
205+
const theme = useStoreValue(themeStore);
206+
return (
207+
<button onClick={() => themeStore.set(theme === "dark" ? "light" : "dark")}>
208+
Theme: {theme}
209+
</button>
210+
);
211+
}
212+
```
213+
</TabItem>
214+
</Tabs>
215+
216+
## Next.js support
217+
218+
Simple store is compatible with Next.js, and is built to handle server-side rendering and client-side hydration gracefully.
219+
220+
- Stores initialize once per server request, making them safe for App Router usage
221+
- Client components hydrate with the store's initial value, preventing mismatch issues
222+
223+
### Special considerations for server components
224+
225+
Stores are built to be reactive in client contexts, and should not be manipulated in server components.
226+
227+
To sync a value from a server component to a store, use the `useEffect` hook to update the store from a client component when it mounts:
228+
229+
```tsx {8-10}
230+
// app/page.tsx
231+
"use client";
232+
233+
import { useEffect } from "react";
234+
import { userStore } from "@/lib/user";
235+
236+
export default function UserProvider({ serverUser }: { serverUser: User }) {
237+
useEffect(() => {
238+
userStore.set(serverUser);
239+
}, [serverUser]);
240+
return null;
241+
}
242+
```
243+
244+
If you need to read the current value of a store in a server component, you can use the `get()` method. This returns the current value of the store when the component is being rendered.
245+
246+
:::note
247+
You cannot call `useStoreValue()` in a server component, since subscriptions are only available in client components.
248+
:::
249+
250+
```tsx
251+
// app/page.tsx
252+
import { counterStore } from "@/lib/counter";
253+
254+
export default function Page() {
255+
const count = counterStore.get(); // OK: read-only on server
256+
return <p>Server-rendered count: {count}</p>;
257+
}
258+
```
259+
260+
## API
261+
262+
### store(initial)
263+
264+
Creates a store with `get`, `set`, `subscribe`, and (for objects and arrays) `select`.
265+
266+
- Parameters: `initial: number | string | boolean | null | undefined | object`
267+
- Returns: `Store<T>` where `T` is inferred from `initial` or supplied via generics
268+
269+
```ts
270+
import { store } from "@simplestack/store";
271+
272+
const counter = store(0);
273+
counter.set((n) => n + 1);
274+
console.log(counter.get()); // 1
275+
276+
// Select parts of a store for objects and arrays
277+
const doc = store({ title: "x" });
278+
const title = doc.select("title");
279+
```
280+
281+
### React
282+
283+
#### useStoreValue(store)
284+
285+
React hook to subscribe to a store and get its current value.
286+
287+
- Parameters: `store: Store<T> | undefined`
288+
- Returns: `T | undefined`
289+
290+
```tsx
291+
import { store } from "@simplestack/store";
292+
import { useStoreValue } from "@simplestack/store/react";
293+
294+
const counterStore = store(0);
295+
296+
function Counter() {
297+
const counter = useStoreValue(counterStore);
298+
return (
299+
<button onClick={() => counterStore.set((n) => n + 1)}>{counter}</button>
300+
);
301+
}
302+
```
303+
304+
## Type Reference
305+
306+
These types are exported for TypeScript users.
307+
308+
- StateObject: `Record<string | number | symbol, any>`
309+
- StatePrimitive: `string | number | boolean | null | undefined`
310+
- Setter: `T | ((state: T) => T)`
311+
- Store:
312+
- `get(): T`
313+
- `set(setter: Setter<T>): void`
314+
- `subscribe(callback: (state: T) => void): () => void`
315+
- `select(key: K): Store<SelectValue<T, K>>`: present only when `T` is an object or array
316+
317+
## Contributing
318+
319+
Contributions are welcome! Please feel free to submit an issue or pull request.
320+
321+
## License
322+
323+
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.

0 commit comments

Comments
 (0)