Skip to content

Commit a9e1f31

Browse files
committed
[docs] New section on Stores in React components
Fixes @226
1 parent 9d31e62 commit a9e1f31

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

site/guides/03_schemas/2_schema_based_typing.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,94 @@ const App = () => (
212212
);
213213
```
214214

215+
## Defining Schema-Based Stores in React Components
216+
217+
One final pattern to be aware of is that you can use the `WithSchemas` type to
218+
create a schema-based Store in a non-rendering React component. This allows you
219+
to create a Store with a schema, define its persistence behavior, publish it
220+
into the context, and even expose domain-specific hooks - all in a single
221+
self-contained file.
222+
223+
There are good examples of this in the [TinyHub](https://tinyhub.org/) project
224+
(see the store components [this
225+
folder](https://github.com/tinyplex/tinyhub/tree/main/client/src/stores)).
226+
227+
If you want to use this pattern your app's top-level will look something like this:
228+
229+
```tsx yolo
230+
export const App = () => {
231+
return (
232+
<Provider>
233+
<MyStore /> {/* This is a schema-based Store component */}
234+
<ActualApp /> {/* This is the actual rendered app */}
235+
</Provider>
236+
);
237+
};
238+
```
239+
240+
The `MyStore` component renders `null` so it doesn't appear visually, but it is
241+
responsible for creating the Store and making it available to the rest of the
242+
app via the Provider context that wraps them both.
243+
244+
The `MyStore.tsx` file might look something like this. (In this simplified case,
245+
we are just typing key values in our Store but of course this would work for a
246+
tabular Store too.)
247+
248+
```tsx yolo
249+
// A unique Id for this Store.
250+
const STORE_ID = 'myStore';
251+
252+
// The schema for this Store.
253+
const VALUES_SCHEMA = {
254+
myStringValue: {type: 'string', default: 'foo'},
255+
myNumericValue: {type: 'number', default: 42},
256+
} as const;
257+
type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA];
258+
259+
// Destructure the ui-react module with the schema applied.
260+
const {useCreateStore, useProvideStore, useCreatePersister, useValue} =
261+
UiReact as UiReact.WithSchemas<Schemas>;
262+
263+
export const MyStore = () => {
264+
// Create the Store and set its schema
265+
const myStore = useCreateStore(() =>
266+
createStore().setValuesSchema(VALUES_SCHEMA),
267+
);
268+
269+
// Create a local storage persister for the Store and start it
270+
useCreatePersister(
271+
settingsStore,
272+
(settingsStore) => createLocalPersister(settingsStore, STORE_ID),
273+
[],
274+
async (persister) => {
275+
await persister.startAutoLoad();
276+
await persister.startAutoSave();
277+
},
278+
);
279+
280+
// Provide the Store for the rest of the app.
281+
useProvideStore(STORE_ID, settingsStore);
282+
283+
// Don't render anything.
284+
return null;
285+
};
286+
```
287+
288+
In that same file, you can also define domain-specific hooks that use an expose
289+
the schema. For example, this hook will be typed to return a string if passed
290+
'myStringValue', and a number if passed 'myNumericValue'.
291+
292+
```tsx yolo
293+
type ValueIds = keyof typeof VALUES_SCHEMA;
294+
export const useSettingsValue = <ValueId extends ValueIds>(valueId: ValueId) =>
295+
useValue<ValueId>(valueId, STORE_ID);
296+
```
297+
298+
This means that the rest of the app can use Values from the Store with correct
299+
types, _and_ the implementation details of the Store are encapsulated in the
300+
single file. For more complex applications, it really helps to keep everything
301+
about the Store in one place.
302+
215303
## Summary
216304

217305
Schema-based typing provides a powerful developer-time experience for checking

0 commit comments

Comments
 (0)