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

selector-action

v1.2.0

Published

State-aware Redux actions with Reselect syntax.

Downloads

94

Readme

State-aware Redux actions with Reselect syntax

Build Status Coverage Status npm version

Basic Usage

import selectorAction from 'selector-action';

// ...

export const reloadActiveItem = selectorAction(
  activeIdSelector,
  (activeId) => ({
    type: 'RELOAD_ACTIVE_ITEM',
    promise: fetch(`//website.com/items/${activeId}`),
  }),
);

Background

selector-action simplifies a common Redux pattern: actions that depend on the current Redux state. For example, when you're reloading an "active" item's data with an API call:

// The active ID is in the Redux state, but we need it to make our API call. Sad!
export function reloadActiveItem(activeId) {
  return {
    type: 'RELOAD_ACTIVE_ITEM',
    promise: fetch(`//website.com/items/${activeId}`),
  };
}

In this example, we have to pass activeId to the action creator, even though it’s actually part of the Redux state! This pollutes our React components with unnecessary props, and makes the action creators more complicated than they need to be. We could try using redux-thunk to eliminat the argument, but that adds a lot of boilerplate:

export function reloadActiveItem() {
  // We can access the current state by returning a thunk.
  return (dispatch, getState) => {
    const state = getState();
    const activeId = activeIdSelector(state);
    // Now we just have to dispatch the action...
    return dispatch({
      type: 'RELOAD_ACTIVE_ITEM',
      promise: fetch(`//website.com/items/${activeId}`),
    });
  };
}

We have to get the state, call selectors on it, and finally dispatch the resulting action. And if we wanted to test this action creator, there's even more boilerplate - we'd have to mock getState, then spy on the dispatch function...it's not pretty.

Using selector-action

selectorAction makes this pattern a breeze. Instead of doing the dispatching and the action-creating ourselves, we’ll pass in a selector and an action creator function that uses the selector's results. The end result is a reloadActiveItem function that's exactly equivalent to the previous example.

import selectorAction from 'selector-action';

// ...

export const reloadActiveItem = selectorAction(
  activeIdSelector,
  (activeId) => ({
    type: 'RELOAD_ACTIVE_ITEM',
    promise: fetch(`//website.com/items/${activeId}`),
  }),
);

As you can see, this looks a lot like the syntax of Reselect. You pass in one or more selectors, then an action creator that takes the selectors’ return values as its arguments. The result of this action creator function is what's dispatched.

Testing

selectorAction exposes the original action creator as an originalActionCreator property of the generated function. This allows for testing the underlying action creator without using a fake state or stubbing dispatch.

describe('reloadActiveItem', () => {
  it('returns an action with the correct type', () => {
    const activeId = 1234;
    const action = reloadActiveItem.originalActionCreator(activeId);
    expect(action.type).to.eql('RELOAD_ACTIVE_ITEM');
  });
});

middleware

If you're already using redux-thunk, you don't need to do anything to start using selectorAction - it’s fully compatible with the thunk middleware.

If you don’t want to use redux-thunk (and there are some good reasons to not want to ), then selector-action provides a middleware for you to use instead:

import { createStore, applyMiddleware } from 'redux';
import selectorActionMiddleware from 'selector-action/middleware';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(selectorActionMiddleware),
);

selector-action's middleware only runs when selectorActions are dispatched. Similar to redux-thunk, it plays nice with other common middlewares like redux-pack.

Advanced features

state as an argument

In addition to the results of the selectors, state is passed in as the last argument to the action creator. If you don't specify any selectors, then state is the only argument. This can be useful for converting actions that already use the getState trick shown above.

export const reloadActiveItem = selectorAction((state) => {
  const activeId = activeIdSelector(state);
  return {
    type: 'RELOAD_ACTIVE_ITEM',
    promise: fetch(`//website.com/items/${activeId}`),
  };
});

Arrays of selectors

Like Reselect, you can pass in an array of selectors instead of passing them as separate arguments. Here is a contrived example demonstrating this feature.

export const awesomeAction = selectorAction(
  [fooSelector, barSelector],
  (foo, bar) => ({ type: 'AWESOME!', payload: { foo, bar } }),
);

selectorAction with arguments

You'll probably run across a case where an action creator needs a mix of selector results and regular arguments to compute an action. For instance, let’s say that the user has entered a new name for the current active item. Your reducer will need both the new name AND the active item’s ID to compute a new state. This can be done by wrapping selectorAction in a higher-order function.

export function setActiveItemName(newName) {
  return selectorAction(activeIdSelector, (activeId) => ({
    type: 'SET_ITEM_NAME',
    payload: { activeId, newName },
  }));
}

License

MIT