Skip to content

Commit

Permalink
Update quick start docs page
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriyyakym committed Dec 22, 2023
1 parent 4171407 commit 5aba7ba
Showing 1 changed file with 25 additions and 11 deletions.
36 changes: 25 additions & 11 deletions docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ slug: /quick-start

# Quick start

Let's write some basic using Awai, every front-end developer faces every day.
Our task is to implement basic users catalog app with following functionaly:
Let's write some basic logics using Awai, every front-end developer faces every day.
Our task is to implement users list app with following functionaly:
- users list
- user details view
- user refetching every 3 seconds
- tracking

Let's use some public [Reqres](https://reqres.in) mock API. Let's assume we have `fetchUsers` and `fetchUser` functions already implemented.
We will use some random public [mock API](https://jsonplaceholder.typicode.com). Let's assume we have `fetchUsers` and `fetchUser` functions already implemented.

You can see the complete working example at [playground](https://codesandbox.io/p/sandbox/awai--users-list-82pdm7).

Expand All @@ -29,7 +29,7 @@ npm install awai awai-react

### Creating state

Awai provides two basic possibilities for keeping a state - [State](/state) and [AsyncState](/async-state). We will need both of them. In state we will store currently viewed user, in AsyncState we will store all users list. Let's not worry about user details view yet.
Awai provides two basic nodes for keeping a state - [State](/state) and [AsyncState](/async-state). We will need both of them. In State we will store currently viewed user id, whereas in AsyncState we will store all users list. Let's not worry about user details view yet.

```ts
const activeUserIdState = state(null);
Expand All @@ -41,20 +41,33 @@ As an initial value for `usersState` we used an async initializer function, whic

For getting state value use `get` method, like `usersState.get()`. Similarly, you need to use `set` method to set the value. For an AsyncState it is possible to set a Promise as a value, which will set state into a `pending` status. AsyncState can have one of three statuses: `pending`, `fulfilled` or `rejected`.
If you use `get` method on state being initialized, the return value is `undefined`.
If you want to be sure about asyncState value, you should use `getPromise` method.

:::info
If you would like to initialize users list state later, you can assign an empty array as an initial value, and set a users promise whenever data is needed.

```ts
const usersState = asyncState([]);

const loadUsers = action(() => {
usersState.set(fetchUsers());
});
```
:::

### Family state

Now, let's create a state, where we will keep users' details. For that purpose Awai provides `familyState` helper. That helper is basically a record of `{ [id]: State }` or `{ [id]: AsyncState }`, depending on an initializer function. In our case it will be family of asyncStates, since all the data will be loaded from API asynchronously.
Now, let's create a state, where we will keep users' details. For that purpose Awai provides `familyState` node. That node is basically a record of `{ [id]: State }` or `{ [id]: AsyncState }`, depending on an initializer function return type. In our case it will be family of asyncStates, since all the data will be loaded from API asynchronously and our initializer returns a promise.

```ts
const usersDetailsFamilyState = familyState((id) => fetchUser(id));
```

At this moment nothing happens with our family state, unless we use `getNode` method. This method will check if state for the requested id exists in our family. If yes, it will return the state node, otherwise it will create a node using `fetchUser` as an initial value.
At this moment nothing happens with our family state, unless we use `getNode(id)` method. This method will check if state for the requested ID exists in our family. If yes, it will return the state node, otherwise it will create a node using `fetchUser(id)` as an initial value.

### Action

For this example we only need `setActiveUserId` action. You may just set state directly using `activeUserIdState.set`, but recommended approach is to wrap it with `action` in order to get access to action events, which will be needed for tracking.
For this example we only need `setActiveUserId` action. You may just set state directly using `activeUserIdState.set`, but recommended approach is to wrap it with `action` in order to get access to action events, which will be needed for triggering scenarios.

```ts
const setActiveUserId = action(id => activeUserIdState.set(id));
Expand All @@ -63,7 +76,7 @@ const setActiveUserId = action(id => activeUserIdState.set(id));

### Effect

Effect is used for reacting to states changes and cleanup any effects.
Effect is used for reacting to states changes and cleanup any effects. We will use it to refetch active user data every 3 seconds, according to our requirements.

```ts
effect([activeUserIdState], (activeUserId) => {
Expand All @@ -85,7 +98,7 @@ effect([activeUserIdState], (activeUserId) => {
```

In this effect, if there is no selected user, we do not want to revalidate any data, so we just return.
Next we get activeUserDetailsState node from family using its ID, and revalidate the user with 3s interval, setting it as a value promise.
Next we get active user details state node from family using its ID, and revalidate the user with 3s interval, setting it as a value promise.

:::info Race conditions
Notice that we set a promise, not a resolved value. This helps with race conditions since Awai will only take care of last set promise.
Expand All @@ -108,7 +121,8 @@ const activeUserDetailsState = selector(
);
```

Now we have an async selector, which reacts to any changes in dependencies states and call our callback to combine resulting value. In our case we pick userDetails state node from familyState by id and return it's promise, so that asyncSelector can handle it.
Now we have an async selector, which reacts to any changes in dependencies states and call our callback, passing it state values as arguments, in order to combine resulting value. In our case we pick userDetails state node from familyState by id and return it's promise, so that asyncSelector can handle it.
As you can see, we have ignored `_userDetailsFamily`, it is done for a safety reasons, since state with some id may still not be available in family, whereas `getNode(id)` assures its existence.

:::info Useful information
Notice, that even though all the dependencies are sync, you can still use an async combining callback, which will result in async selector:
Expand All @@ -126,7 +140,7 @@ selector(

### Tracking

For tracking it's best to use Scenarios, which are a handy tool to react to any AwaiEvents, promises or their combination.
For tracking it's best to use Scenarios, which are a handy tool to react to any AwaiEvents, promises or their combination. And helps to extract tracking/additional logics from business logics.

```ts
scenario(setActiveUserId.events.invoked, (id) => {
Expand Down

0 comments on commit 5aba7ba

Please sign in to comment.