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

redux-selector-action

v1.0.1

Published

Selector Actions for Redux.

Downloads

5

Readme

Redux Selector Action

npm package

Same Redux "selectors" but for actions, inspired by reselect.

  • Selector Actions can be dispatched as any other Redux action.
  • Selector Actions accept store selectors to eventually inject their output to the given action creator.
  • Selector Actions reduce the data your "containers" (components connected to redux store) need to pass.

A selector-action creator accepts a list of selectors and a regular action creator

container/actions.js

import { createSelectorAction, getPlaceholder } from 'redux-selector-action';

const getCsrfToken = state => state.csrfToken;
const getCurrency = state => state.currency;
const getLang = state => state.lang;


export const fetchOrder = createSelectorAction(
  getCsrfToken,
  getPlaceholder, // placeholder for order id
  getCurrency,
  getLang,
  (token, orderId, currency, lang) => ({
    type: 'fetch_order_request',
    payload: {
      // ...
    }
  })
);

// fetchOrder(123); first arg fills first placeholder, etc.

container/index.js

import {fetchOrder} from './actions';

const mapDispatchToProps = dispatch => {
  return {
    fetchOrder: id => {
      dispatch(fetchOrder(id));
    }
  }
}

index.js

import { applyMiddleware, createStore, compose } from 'redux';
import { reduxSelectorActionMiddleware } from 'redux-selector-action';
import rootReducer from './reducers';                                     
import {fetchOrder} from './container/actions';

const middlewareEnhancer = applyMiddleware(reduxSelectorActionMiddleware);
const composedEnhancers = compose(middlewareEnhancer);
const initialState = undefined;

const store = createStore(rootReducer, initialState, composedEnhancers);

store.dispatch(fetchOrder(123));

Table of Contents

Installation

npm install redux-selector-action

Motivation for Selector Actions

Containers include in most cases some props, which they get just to pass to actions creators. In order to avoid redundant data (props) passed to those containers, there should be a way for actions to get their data from store. This way would let us create actions which accept only the data the container holds.

Check out the following action creator:

It seems that every update of any order field will require me to pass token and orderId, which are redundant from the container's perspective.

export function updateOrderCurrencyAction(token, orderId, currency) {
  return {
    type: 'update_order_currency',
    payload: {
      // ...
    }
  };
}

Let's fix this.

API

createSelectorAction(...selectors | [...selectors], resultFunc)

This function accept a list of selectors, or an array of selectors, computes their output against the store's state, and inject them as arguments to the given resultFunc.

getPlaceholder selector will be handled separately.

import { createSelectorAction, getPlaceholder } from 'redux-selector-action';
import {updateOrderCurrencyAction} from './actions';

const getCsrfToken = state => state.csrfToken;
const getOrderId = state => state.orderId;

// We accept array of selectors too! choose your preferred way.
export const updateOrderCurrency = createSelectorAction(
  [getCsrfToken, getOrderId, getPlaceholder /* currency */],
  updateOrderCurrencyAction,
);

// Now you can pass only the relevant data
updateOrderCurrency('USD');

getPlaceholder()

This is a built-in selector, which you can use as part of your action creator's selectors. Once you pass it as a dependency, instead of injecting the output of this selector, we save its position in the dependency list (selectors) for an arg, which will be sent once you call the selector action.

In case you have an action creator with an "options" argument (meaning an object which maps arg names to their values), you can use the following syntax:

import { createSelectorAction, getPlaceholder } from 'redux-selector-action';
import { getCsrfToken, getCurrency, getLang } from './selectors';

export const fetchOrder = createSelectorAction(
  // Map the arg names to selectors, then your action creator will get their values:
  getPlaceholder({
    token: getCsrfToken, 
    currency: getCurrency, 
    lang: getLang,
  }),
  ({token, orderId, currency, lang}) => ({
    type: 'fetch_order_request',
    payload: {
      // ...
    }
  })
);

// fetchOrder({orderId: 123});

reduxSelectorActionMiddleware()

This is a redux middleware which handles our build-in selector actions. In order to make everything work, you should add it to your store enhancers, the position does not matter.

import { applyMiddleware, createStore, compose } from 'redux';
import { reduxSelectorActionMiddleware } from 'redux-selector-action';
import rootReducer from './reducers';

const middlewareEnhancer = applyMiddleware(reduxSelectorActionMiddleware);
const composedEnhancers = compose(middlewareEnhancer);
const initialState = undefined;

const store = createStore(rootReducer, initialState, composedEnhancers);

FAQ

Q: Can I use this package without Redux?

A: No. Even though this package has no dependency on Redux, it was designed to be used with Redux. It means we expect for example that our middleware will be called with Redux store api (store.getState(), store.dispatch()).

Q: Can I use this package without Reselect?

A: Yes. This package has no dependency on Reselect, you can work with any selectors you want, eventually they are just functions that accept state.

Q: My action accepts many args that can't be injected, should I pass many getPlaceholders?

A: Not necessarily. All args you pass to the created selector action will be injected to the placeholders. But if you pass more args than placeholders, then they will be appended too.

Q: How can I test a selector action?

Every selector action keeps a reference to the given selectors and the action creator, as .dependencies and .resultFunc respectively.

For example if you have the following selector action:

src/selectorActions.js

export const getFirst = state => 1;
export const getSecond = state => 2;

export const mySelectorAction = createSelectorAction(
  getFirst,
  getSecond,
  getPlaceholder,
  (first, second, third) => ({ type: '', payload: { /* ... */ }})
)

You can test it this way:

test/selectorActions.js

import { mySelectorAction } from '../src/selectorActions';

// test the selectors themselves...
test("getFirst", () => { /* ... */ });
test("getSecond", () => { /* ... */ });

test("mySelectorAction", () => {
  // check the dependencies are as expected
  assert(mySelectorAction.dependencies).toEqual([getFirst, getSecond, getPlaceholder]);
  // check the resultFunc output is as expected
  assert(mySelectorAction.resultFunc(1, 2, 3)).toMatchSnapshot();
})

License

MIT