Skip to content

Commit

Permalink
docs(store): rewrite readme and all of docs
Browse files Browse the repository at this point in the history
  • Loading branch information
DawidWraga committed Apr 13, 2024
1 parent 48845c4 commit 2fde198
Show file tree
Hide file tree
Showing 12 changed files with 883 additions and 199 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ dist-ssr
server/dist
public/dist
.turbo
coverage
9 changes: 9 additions & 0 deletions apps/docs/pages/store/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
"overview": {
"title": "Overview"
},
"store-anatomy": {},
"defining-stores": {},
"reading-state": {},
"updating-state": {},
"onChange-and-effects": {},
"local-state-management": {},
"options-and-middleware": {},
"extensions": {},
"async-usage": {},
"todo-example": {
"title": "Todo Example",
"theme": {
Expand Down
80 changes: 80 additions & 0 deletions apps/docs/pages/store/async-usage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Async usage

Davstack store can be combined with davstack service to manage async state. Here is an example of how to use davstack store with davstack service to manage async state.

1. Export the trpc react query api utils from your trpc provider file

```tsx
export let apiUtils = null as unknown as ReturnType<typeof api.useUtils>;

function InitApiClient() {
const actualApiUtils = api.useUtils();

useEffect(() => {
apiUtils = actualApiUtils;
}, [actualApiUtils]);

return null;
}

export function TRPCReactProvider(props: { children: React.ReactNode }) {
// ... other code
return (
<api.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{props.children}
<InitApiClient />
</QueryClientProvider>
</api.Provider>
);
}
```

This allows you to access the api utils from anywhere in your app.

2. Create a store that manages the async state

```tsx
import { store } from '@davstack/store';

export const notificationsStore = store({
subscription: null as PushSubscription | null,
registration: null as ServiceWorkerRegistration | null,
})
.computed((store) => ({
isSubscribed: () => Boolean(store.subscription),
}))
.extend((store) => {
async function init() {
const registration = await navigator.serviceWorker.ready;

try {
checkPushNotificationIsSupported();

const subscription = await registration.pushManager.getSubscription();

// use the api utils to make a request to the server
await apiUtils.notification.checkSubscription.fetch({
endpoint: subscription.endpoint,
});

store.subscription.set(subscription);
} catch (error) {
console.error('Error initializing subscription:', error);
}
}

return {
/**
* Initializes the store, should only be place once in root layout
*/
Init() {
useEffect(() => {
init();
}, []);

return null;
},
};
});
```
69 changes: 69 additions & 0 deletions apps/docs/pages/store/defining-stores.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Callout } from 'nextra/components';

# Defining Stores

To create a store, you can use the `store()` function and pass in the initial state:

```tsx
import { store } from '@davstack/store';

const counterStore = store(0);
```

Alternatively, you can use `store.state()` to define the initial state:

```tsx
const counterStore = store().state(0);
```

<Callout type="info" emoji="ℹ️">
`store(initialState)` and `store.state(initialState)` are equivalent and can be used interchangeably.

</Callout>

## Defining Actions

Actions are functions that modify the store's state. They can be defined using the `actions` method:

```tsx
const userStore = store()
.state({
name: 'John',
age: 25,
})
.actions((store) => ({
incrementAge() {
store.age.set(store.age.get() + 1);
},
}));

// calling actions
userStore.incrementAge();
```

## Defining Computed Properties

Computed properties are derived values that are automatically updated when the store's state changes. They can be defined using the `computed` method:

```tsx
const userStore = store()
.state({
name: 'John',
age: 25,
})
.computed((store) => ({
fullName: () => `${store.name.get()} Doe`,
}));

// accessing computed properties
const fullName = userStore.fullName.get();
```

<Callout type="info" emoji="ℹ️">
How do computed properties work?
</Callout>

- The "store" passed into the computed callback is a proxy.
- When you call `store.computedValue.get()`, then the store will be passed to the computed function like normal.
- When you call `store.computedValue.use()`, then the store passed to computed function will detect any `store.get()` calls inside the computed function, and will replace them with `store.use()` calls.
- This means that the value will be re-evaluated whenever any of the dependencies change, but it does not currently cache the result.
107 changes: 107 additions & 0 deletions apps/docs/pages/store/extensions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Extensions

Extensions allow you to assign any additional properties to the store, while keeping the store definition self-contained and reusable.

Under the hood, `actions`, `computed` and `effects` all wrap around the `extend` method.

However, you can also use `extend` directly to add any custom properties to the store, which don't direclty fit into the state, actions or computed properties.

## Basic usage example

```tsx
import { store } from '@davstack/store';

const userStore = store()
.state({
name: 'John',
age: 25,
})
.extend((store) => ({
isAdmin: false,
}));

// accessing the extension

const isAdmin = userStore.isAdmin.get();
```

## Example usage with hooks

```tsx

const altStore = store({
searchTerm: '',
}).extend((store) => ({
useFilteredBooks: () => {
const searchTerm = store.searchTerm.use();
// use react query or any other data fetching library here

},

```
## Example usage with components
```tsx
import { store } from '@davstack/store';

export const notificationsStore = store({
subscription: null as PushSubscription | null,
registration: null as ServiceWorkerRegistration | null,
}).
.computed((store) => ({
isSubscribed: () => Boolean(store.subscription),
}))
.extend((store) => {
async function init() {
const registration = await navigator.serviceWorker.ready;

try {
checkPushNotificationIsSupported();

const subscription = await registration.pushManager.getSubscription();

if (!subscription) {
console.log("No subscription found");
return;
}


await apiUtils!.notification.checkSubscription.fetch({
endpoint: subscription.endpoint,
});

store.subscription.set(subscription);
} catch (error) {
console.error("Error initializing subscription:", error);
}
}

return {
/**
* Initializes the store, should only be place once in root layout
*/
Init() {
useEffect(() => {
init();
}, []);

return null;
}
};
});

// app/layout.tsx
import { notificationsStore } from './notificationsStore';

export default function Layout({ children }:{children: React.ReactNode}) {
return (
<div>
{children}
<notificationsStore.Init>
</div>
);
}


```
Loading

0 comments on commit 2fde198

Please sign in to comment.