# dx-saga

> Warning: This package is in beta and subject to change frequently check back often for the latest.

Latest version **0.0.1-beta.6** (published 2021-08-13) · 0 weekly downloads

## Install

```sh
npm install dx-saga
pnpm add dx-saga
yarn add dx-saga
bun add dx-saga
```

## Health

**Score 20/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities.

Warnings: low downloads; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.0.1-beta.6 |
| Published | 2021-08-13 |
| First published | 2021-08-01 |
| Weekly downloads | 0 |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 3 |
| Unpacked size | 643.3 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1 |
| Maintainers | sangchatb |

## Links

- npm: https://www.npmjs.com/package/dx-saga
- Repository: https://github.com/sangchatb/dx-saga
- Homepage: https://github.com/sangchatb/dx-saga#readme
- Issues: https://github.com/sangchatb/dx-saga/issues
- npm.io page: https://npm.io/package/dx-saga

## Dependencies (3)

- [debug](https://npm.io/package/debug.md) 4.3.2
- [@types/debug](https://npm.io/package/@types/debug.md) 4.1.7
- [typed-redux-saga](https://npm.io/package/typed-redux-saga.md) 1.3.1

## Recent versions

- 0.0.1-beta.6 (latest) — 2021-08-13
- 0.0.1-beta.5 — 2021-08-08
- 0.0.1-beta.4 — 2021-08-04
- 0.0.1-beta.3 — 2021-08-01
- 0.0.1-beta.2 — 2021-08-01
- 0.0.1-beta.1 — 2021-08-01

## README

# dx-saga

Warning: This package is in beta and subject to change frequently check back often for the latest.

[![npm version](https://badge.fury.io/js/dx-saga.svg)](https://www.npmjs.com/package/dx-saga)

dx-saga is a JavaScript library that allows redux-sagas to run on differences in state as opposed to actions.

- `selectorChannel`
  - run sagas when a subset of state changes as opposed to when actions are dispatched
  - prevent extraneous side-effects by only running sagas when the subset of state used as inputs changes
  - simplify sagas that take multiple actions as inputs by watching the state instead
  - nextAction = F(select(state), saga) where select(state) ⊂ state when select(State) != select(nextState)
- `useSaga`
  - Start and stop sagas when components mount and unmount
  - ensure effects, like takeLatest, have their own state per UI component
  - provide component identity and other props using the `ownProps` option
  - optionally provide a separate `context` from the saga middleware saga
- serialize execution of code blocks using `monitor.lock`

## `selectorChannel` usage

```typescript
const getSearchChanges = (state: RootState): SearchChanges => {
  const { text, caseSensitive } = state.search;
  return { text, caseSensitive };
};

function* handleSearchChanges(searchChanges: SearchChanges) {
  // ...
}

function* watchSearchSagas() {
  /* HANDLE CHANGES TO STATE AS OPPOSED TO ACTIONS. ACCEPTS ANY SELECTOR */
  const searchChanges = selectorChannel(getSearchChanges);
  /* USE WHERE PATTERNS ARE USED */
  yield* takeLatest(searchChanges, handleSearchChanges);
}
```

## Installation

```bash
# NPM
npm install dx-saga
```

or

```bash
# YARN
yarn add dx-saga
```

## Selector Channels

### Motivation for selector channels

#### [`https://codesandbox.io/s/take-latest-action-pattern-tux36?file=/src/index.tsx`](https://codesandbox.io/s/take-latest-action-pattern-tux36?file=/src/index.tsx)

```ts
const getSearchChanges = (state: RootState): SearchChanges => {
  const { text, caseSensitive } = state.search;
  return { text, caseSensitive };
};

function* handleSearchChanges() {
  debug("delay");
  yield* delay(500);
  const searchChanges = yield* select(getSearchChanges);
  debug(`handleSearchChanges ${JSON.stringify(searchChanges)}`);
}

function* watchSearchSagas() {
  yield* takeLatest(
    [
      searchSlice.actions.onChangeCaseSensitive.type,
      searchSlice.actions.onChangeText.type,
    ],
    handleSearchChanges
  );
}

sagaMiddleware.run(watchSearchSagas);

/* DISPATCH ACTION1 */
const action1 = searchSlice.actions.onChangeText("foo");
store.dispatch(action1);

/* IMMEDIATELY DISPATCH ACTION2. IT WILL CANCEL ACTION1'S SIDE EFFECTS
    SEE THE CONSOLE  */
const action2 = searchSlice.actions.onChangeCaseSensitive(true);
store.dispatch(action2);
```

In the above example, `takeLatest` watches for action types to trigger sagas. This works well when it's one action. When it's more than one, the saga will not have all the state it needs and it may be triggered unnecessarily when state in the action payload either contains extra data or doesn't result in a change to state. `selectorChannel` avoids each of a these cases, resulting in simpler code while leveraging the saga API.

### Creating a selectorChannel

We would like to replace `takeLatest(pattern, saga)`, which triggers when events occur, with `takeLatest(channel, saga)` that triggers when changes occur in the subset of state returned by a selector.

`dx-saga` provides a function `makeSelectorChannelFactory` that produces a function `selectorChannel` to create selector channels. Its accepts any selector and will `emit` when subset of state returned by the selector changes. Each of these emissions can be used by existing saga API to `takeEvery`, `takeLatests`, etc.

Let's define a selectorChannel named `searchChanges` to replace the action-pattern version above:

#### [`https://codesandbox.io/s/selector-channel-qepep?file=/src/index.tsx:1264-1412`](https://codesandbox.io/s/selector-channel-qepep?file=/src/index.tsx:1264-1412)

```ts
import { makeSelectorChannelFactory } from "dx-saga";

//...

const selectorChannel = makeSelectorChannelFactory(store);

const getSearchChanges = (state: RootState): SearchChanges => {
  const { text, caseSensitive } = state.search;
  return { text, caseSensitive };
};

function* handleSearchChanges(searchChanges: SearchChanges) {
  debug("delay");
  yield* delay(500);
  debug(`handleSearchChanges ${JSON.stringify(searchChanges)}`);
}

function* watchSearchSagas() {
  /* HANDLE CHANGES TO STATE AS OPPOSED TO ACTIONS. ACCEPTS ANY SELECTOR */
  const searchChanges = selectorChannel(getSearchChanges);
  /* USE WHERE PATTERNS ARE USED */
  yield* takeLatest(searchChanges, handleSearchChanges);
}

sagaMiddleware.run(watchSearchSagas);

/* DISPATCH ACTION1 */
const action1 = searchSlice.actions.onChangeText("foo");
store.dispatch(action1);

/* IMMEDIATELY DISPATCH ACTION2. IT WILL CANCEL ACTION1'S SIDE EFFECTS
    SEE THE CONSOLE  */
const action2 = searchSlice.actions.onChangeCaseSensitive(true);
store.dispatch(action2);
```

In the example above, `searchChanges` is a `selectorChannel`. It tracks differences in the state provided by `getSearchChanges`. It's provided to `takeLatest` which will trigger `handleSearchChanges` when it detects changes. Since it's provided as a channel, any `takeX` effect can be used. `takeLatest` will also cancel any `handleSearchChanges` side-effects that are still executing.

## useSaga

Coming Soon

## Monitors

Coming Soon

## Prior Art

- rxjs - Requires learning new control flow semantics. I found it very complex for simple tasks.
- redux-saga - Preserves well known control flow semantics for async tasks
- selector-channel - https://github.com/redux-saga/redux-saga/issues/1694 - opted for an implementation that compares the diff outside of sagas.
- more to come...

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

## License

[MIT](https://choosealicense.com/licenses/mit/)

---
_Source: https://npm.io/package/dx-saga · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
