Skip to content

Commit 6e285c9

Browse files
committed
add new useSelector function
1 parent 80ade5f commit 6e285c9

File tree

1 file changed

+82
-16
lines changed

1 file changed

+82
-16
lines changed

www/src/content/docs/store.mdx

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ You can then subscribe to a sub-store the same way you subscribe to a parent sto
213213
</TabItem>
214214
</Tabs>
215215

216+
:::tip
217+
`useStoreValue` also accepts a selector function for dynamic keys or computed values:
218+
219+
```tsx
220+
const title = useStoreValue(documentStore, (s) => s.notes[id]?.title);
221+
```
222+
223+
[📚 See the API reference to learn more.](#usestorevaluestore-selector)
224+
:::
225+
216226
## Next.js support
217227

218228
Simple store is compatible with Next.js, and is built to handle server-side rendering and client-side hydration gracefully.
@@ -280,12 +290,14 @@ const title = doc.select("title");
280290

281291
### React
282292

283-
#### useStoreValue(store)
293+
#### useStoreValue(store, selector?)
284294

285-
React hook to subscribe to a store and get its current value.
295+
React hook to subscribe to a store and get its current value. Optionally pass a selector function to derive a value from the store.
286296

287-
- Parameters: `store: Store<T> | undefined`
288-
- Returns: `T | undefined`
297+
- Parameters:
298+
- `store: Store<T> | undefined`
299+
- `selector?: (state: T) => R` - optional function to select/compute a value
300+
- Returns: `R | T | undefined`
289301

290302
```tsx
291303
import { store } from "@simplestack/store";
@@ -301,6 +313,67 @@ function Counter() {
301313
}
302314
```
303315

316+
With a selector:
317+
318+
```tsx
319+
const documentStore = store({
320+
notes: {
321+
"1": { title: "First" },
322+
"2": { title: "Second" },
323+
},
324+
});
325+
326+
function NoteTitle({ id }: { id: string }) {
327+
// Only re-renders when this specific note's title changes
328+
const title = useStoreValue(documentStore, (s) => s.notes[id]?.title);
329+
return <h1>{title}</h1>;
330+
}
331+
332+
function NoteCount() {
333+
// Compute derived values inline
334+
const count = useStoreValue(documentStore, (s) => Object.keys(s.notes).length);
335+
return <span>{count} notes</span>;
336+
}
337+
```
338+
339+
#### useShallow(selector)
340+
341+
Wraps a selector with shallow equality comparison. Use this when your selector returns a new array or object reference on each call.
342+
343+
**The problem:** selectors that return new references (like `Object.values()`, array `filter()`, or object spreads) cause infinite re-renders because React sees a "new" value each time.
344+
345+
```tsx
346+
import { useStoreValue, useShallow } from "@simplestack/store/react";
347+
348+
// ❌ BAD: Creates new array reference each render → infinite loop
349+
const [title, author] = useStoreValue(noteStore, (s) => [s.title, s.author]);
350+
351+
// ✅ GOOD: useShallow compares array contents, stable reference
352+
const [title, author] = useStoreValue(noteStore, useShallow((s) => [s.title, s.author]));
353+
```
354+
355+
More examples:
356+
357+
```tsx
358+
// Filtering creates new array
359+
const drafts = useStoreValue(
360+
docStore,
361+
useShallow((s) => s.notes.filter((n) => n.isDraft))
362+
);
363+
364+
// Spreading creates new object
365+
const meta = useStoreValue(
366+
docStore,
367+
useShallow((s) => ({ title: s.title, author: s.author }))
368+
);
369+
370+
// Object.keys/values/entries create new arrays
371+
const ids = useStoreValue(
372+
docStore,
373+
useShallow((s) => Object.keys(s.notes))
374+
);
375+
```
376+
304377
## Type Reference
305378

306379
These types are exported for TypeScript users.
@@ -309,15 +382,8 @@ These types are exported for TypeScript users.
309382
- StatePrimitive: `string | number | boolean | null | undefined`
310383
- Setter: `T | ((state: T) => T)`
311384
- 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.
385+
- `get(): T` - Get the current value of the store.
386+
- `set(setter: Setter<T>): void` - Set the value directly or by using a function that receives the current state.
387+
- `subscribe(callback: (state: T) => void): () => void` - Subscribe with a callback. Returns an unsubscribe function.
388+
- `select(key: K): Store<SelectValue<T, K>>` (present only when `T` is an object or array) - Select a key or array index of the store. Returns a nested Store.
389+
- `getInitial(): T` - Get the initial state the store was created with. Used internally for SSR resume-ability.

0 commit comments

Comments
 (0)