Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/runjuu/mst-effect
💫 Designed to be used with MobX-State-Tree to create asynchronous actions using RxJS
https://github.com/runjuu/mst-effect
async asynchronous-programming mobx mobx-state-tree mst observable promise react redux-observable rxjs state-management stream
Last synced: 15 days ago
JSON representation
💫 Designed to be used with MobX-State-Tree to create asynchronous actions using RxJS
- Host: GitHub
- URL: https://github.com/runjuu/mst-effect
- Owner: runjuu
- License: mit
- Created: 2020-07-19T08:24:09.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2024-08-30T08:58:21.000Z (3 months ago)
- Last Synced: 2024-10-26T11:55:59.950Z (19 days ago)
- Topics: async, asynchronous-programming, mobx, mobx-state-tree, mst, observable, promise, react, redux-observable, rxjs, state-management, stream
- Language: TypeScript
- Homepage:
- Size: 1.46 MB
- Stars: 27
- Watchers: 3
- Forks: 2
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
`mst-effect` is designed to be used with MobX-State-Tree to create asynchronous actions using RxJS. In case you haven't used them before:
> `MobX-State-Tree` is a full-featured reactive state management library that can **structure the state model** super intuitively.
>
`RxJS` is a library for composing asynchronous and event-based programs that provides the best practice to **manage async codes**.If you are still hesitant about learning `RxJS`, check the examples below and play around with them. I assure you that you'll be amazed by what it can do and how clean the code could be.
Already using `MobX-State-Tree`? Awesome! `mst-effect` is 100% compatible with your current project.
## Examples
- [Fetch data](https://codesandbox.io/s/fetch-data-i9hqb?file=/src/app.tsx)
- [Fetch with token](https://codesandbox.io/s/fetch-with-token-rbveh?file=/src/app.tsx)
- [Handle user input](https://codesandbox.io/s/handle-user-input-ef1pt?file=/src/app.tsx)
- [Mutually exclusive actions](https://codesandbox.io/s/mutually-exclusive-actions-ylqlf?file=/src/app.tsx)
- [Back pressure](https://codesandbox.io/s/backpressure-ulu1y?file=/src/app.tsx)## Installation
`mst-effect` has peer dependencies of [mobx](https://www.npmjs.com/package/mobx), [mobx-state-tree](https://www.npmjs.com/package/mobx-state-tree) and [rxjs](https://www.npmjs.com/package/rxjs), which will have to be installed as well.
##### Using [yarn](https://yarnpkg.com/en/package/mst-effect):
```bash
yarn add mst-effect
```##### Or via [npm](https://www.npmjs.com/package/mst-effect):
```bash
npm install mst-effect --save
```## Basics
**`effect`** is the core method of `mst-effect`. It can automatically manage subscriptions and execute the emitted actions. For example:
```ts
import { types, effect, action } from 'mst-effect'
import { map, switchMap } from 'rxjs/operators'const Model = types
.model({
value: types.string,
})
.actions((self) => ({
fetch: effect(self, (payload$) => {
function setValue(value: string) {
self.value = value
}return payload$.pipe(
switchMap((url) => fetch$(url)),
map((value) => action(setValue, value)),
)
}),
}))
```#### Import location
As you can see in the example above, `types` need to be imported from `mst-effect`([Why?](#why-we-need-to-import-types-from-mst-effect)).
#### The definition of the `effect`
The first parameter is the model instance, as `effect` needs to unsubscribe the [`Observable`](https://rxjs-dev.firebaseapp.com/api/index/class/Observable) when the model is destroyed.
The second parameter, a factory function, can be thought of as the `Epic` of [redux-observable](https://redux-observable.js.org/docs/basics/Epics.html). The factory function is called only once at model creation. It takes a stream of payloads and returns a stream of actions. — **Payloads in, actions out.**
Finally, `effect` returns a function to feed a new value to the `payload$`. In actual implementation code, it's just an alias to `subject.next`.
#### What is `action`?
`action` can be considered roughly as a higher-order function that takes a callback function and the arguments for the callback function. But instead of executing immediately, it returns a new function. Action will be immediately invoked when emitted.
```ts
function action(callback, ...params): EffectAction {
return () => callback(...params)
}
```## API Reference
### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/effect/effect.ts) effect
`effect` is used to manage subscriptions automatically.
```ts
type ValidEffectActions = EffectAction | EffectAction[]type EffectDispatcher
= (payload: P) => void
function effect
(
self: AnyInstance,
fn: (payload$: Observable) => Observable,
): EffectDispatcher
````payload$` emits data synchronously when the function returned by the effect is called. The returned `Observable` will automatically subscribed by `effect`
### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/effect/doll-effect.ts) dollEffect
```ts
type ValidEffectActions = EffectAction | EffectAction[]type DollEffectDispatcher
= (
payload: P,
handler?: (resolve$: Observable) => Observable,
) => Promisetype SignalDispatcher = (value: S) => void
function dollEffect
(
self: AnyInstance,
fn: (
payload$: Observable,
signalDispatcher: SignalDispatcher,
) => Observable,
): DollEffectDispatcher
````dollEffect` is almost identical with `effect`. The primary difference is `DollEffectDispatcher` will return a `Promise` which is useful when you want to report some message to the caller. The `Promise` will fulfill when `SignalDispatcher` being invoked ([example](https://codesandbox.io/s/report-fetch-status-to-caller-d6gmh?file=/src/app.tsx)). Also, you can use the `handler` to control when and what the `Promise` should resolve ([example](https://codesandbox.io/s/report-fetch-status-to-caller-2-1eogq?file=/src/app.tsx)).
### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/signal/index.ts) signal
```ts
export function signal(
self: AnyInstance,
fn?: (payload$: Observable) => Observable,
): [Observable, (payload: P) => void]
````signal` is an encapsulation of the [`Subject`](https://rxjs-dev.firebaseapp.com/api/index/class/Subject). You can use the second parameter to do some processing of the output data.
### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/reaction$/index.ts) reaction$
```ts
export function reaction$(
expression: (r: IReactionPublic) => T,
opts?: IReactionOptions,
): Observable<{ current: T; prev: T; r: IReactionPublic }>
````reaction$` encapsulates the [`reaction`](https://mobx.js.org/reactions.html#reaction) method from `mobx`. When the returned value changes, it will emit the corresponding data to the returned `Observable`.
## Recipes
#### Error Handling
When an error occurred in `Observable`, `effect` will re-subscribe the `Observable` (will not re-run the factory function). The common practice is to use the [`catchError`](https://rxjs-dev.firebaseapp.com/api/operators/catchError) operator for error handling. Check [fetch data](https://codesandbox.io/s/fetch-data-i9hqb?file=/src/app.tsx) example for more detail.
#### Cancellation
You can combine `signal` and [`takeUntil()`](https://rxjs-dev.firebaseapp.com/api/operators/takeUntil) operator to cancel an `Observable`. Check [mutually exclusive actions](https://codesandbox.io/s/mutually-exclusive-actions-ylqlf?file=/src/app.tsx) example for more detail.
## FAQ
#### Why we need to import `types` from `mst-effect`
Currently, `mobx-state-tree` does not support modifying the model outside of actions.
`mst-effect` overrides `types.model` so that the model can be modified in an asynchronous process.
Because `mst-effect` re-export all the variables and types in `mobx-state-tree`, you can simply change the import location to `mst-effect`.```diff
- import { types, Instance } from 'mobx-state-tree'
+ import { types, Instance } from 'mst-effect'
```