npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

dx-saga

v0.0.1-beta.6

Published

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

Downloads

9

Readme

dx-saga

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

npm version

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

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

# NPM
npm install dx-saga

or

# YARN
yarn add dx-saga

Selector Channels

Motivation for selector channels

https://codesandbox.io/s/take-latest-action-pattern-tux36?file=/src/index.tsx

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

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