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));
npm install redux-selector-action
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
andorderId
, which are redundant from the container's perspective.
export function updateOrderCurrencyAction(token, orderId, currency) {
return {
type: 'update_order_currency',
payload: {
// ...
}
};
}
Let's fix this.
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');
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});
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);
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()).
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.
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.
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();
})