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-smart-action

v2.0.0

Published

Actions can be smart, too!

Downloads

26

Readme

Redux Smart Action

SmartActions add a layer on top of actions to do things depending on whether an action would modify the state. When a SmartAction is called, it returns an object instead of dispatching immediately. The returned object tells you whether executing it would change the state, and a method to execute it.

For instance, say you have a menu item with an enabled flag, then you can do:

// Assuming remove is an action that dispatches only if something can be
// removed
const deferredRemove = remove();

const menuItem = {
  enabled: deferredRemove.canExec,
  onClick: deferredRemove.exec
};

addMenuItem(menuItem);

They can do a lot more, like composing and mutiple dispatches. For instance, you can define actions such as these:

const push = value => new SmartAction(dispatch => {
  dispatch({type: 'PUSH', value});
});

// This will only notify the subscribers once!
const pushMutiple = (...values) => new SmartAction(dispatch => {
  values.forEach(v => dispatch(push(v)).exec());
});

In some ways, SmartAction moves some business logic from reducers to actions. This may not be everyone's cup of tea, but I personally like it because my reducers can get pretty big. For instance the previous example could be done by adding a canRemove property to the state, but I find it tedious and not as elegant.

Install

$ npm install redux-smart-action

Actions

To create a SmartAction, you must return a SmartAction instance from within a regular action:

import {SmartAction} from 'redux-smart-action';

const push = value => new SmartAction(dispatch => {
  dispatch({type: 'PUSH', value});
});

const pop = () => new SmartAction((dispatch, getState) => {
  if (getState().length) {
    dispatch({type: 'POP'});
  }
});

The SmartAction can make decisions based on the current state via getState and uses dispatch to dispatch actions. It can also dispatch other SmartActions, allowing complex composing and reusability of actions.

For instance:

// This action will push all given arguments
const pushMutiple = (...values) => new SmartAction(dispatch => {
  values.forEach(v => dispatch(push(v)).exec());
});

It can call dispatch multiple times and the store's subscribers will only be notified once, or not at all if the state didn't change.

Options

The constructor expects the following parameters:

new SmartAction(func, branch = true, deepEqual = true);

branch

If not specified, branch is set to true. If branch is true, calling an action will create a temporary store, dispatch and getState are wired to that temporary store. When exec is called, if the state of the temporary store had changed, the state of the original store is replaced by the state of the temporary store. This is the safest option because, whenever an action calls getState, it gets a state which includes any previous mutations by the action.

For instance, this is perfectly safe to do if branch is true:

const pushTimes = (value, times) => new SmartAction((dispatch, getState) => {
  const initialLength = getState().length;
  while (getState().length < initialLength + times) {
    dispatch(push(value)).exec();
  }
});

The down side is that a temporary store is created, so it uses memory.

On the other hand, if branch is set to false, a temporary store is not created so it is more memory efficient. However in this case it is not safe to getState because it doesn't include any previous mutations by the action. With branch set to false, the previous example would run into an infinite loop! Usually, when branch is set to false, getState should only be called once at the beginning of the action before it starts calling dispatch.

deepEqual

If not specified, deepEqual is set to true. It is only relevant when branch is true. If deepEqual is true, deep-equal is used to check if the state changed, so if dispatch is called with values canceling each other, the state will not update and the subscribers won't be notified. This is a nice feature but can be a little expensive.

On the other hand, if deepEquasl is false, a strict equality is used to compare the previous state to the next state. This is must faster, but subscribers can be notified even if the final state is the same as the original.

Middleware

In order for SmartActions to work with a store, you must use applySmartMiddleware instead of redux's applyMiddleware.

This is because the middlewares must have access to extra functions to branch a store which are not provided by redux.

applySmartMiddleware is compatible with normal middlewares, so you don't need to use applyMiddleware at all.

Example:

import {createStore} from 'redux';
import {applySmartMiddleware} from 'redux-smart-action';

const createStoreWithMiddleware = applySmartMiddleware(
  smartActionMiddleware
)(createStore);

function createSmartStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

const store = createSmartStore();

Executing actions

Dispatching an action does not execute it. Instead it returns an object that can be used to determine if the action can be executed (meaning it would change the state) as well as a function to executed it.

For instance:

store.dispach(push(1));

Returns an object:

{
  canExec: true, // True if the action would modify the state
  exec()         // Execute the action (does nothing if canExec is false)
}

It's up to you to decide whether you want to execute the action.

The exec method of the returned object is always safe to call. It returns the value of canExec, allowing interesting patterns.

Patterns

Given the reducer:

function reducer(state = [], action) {
  let nextState;
  switch (action.type) {
  case 'PUSH':
    nextState = state.slice();
    nextState.push(action.value);
    return nextState;
  case 'POP':
    nextState = state.slice();
    nextState.pop();
    return nextState;
  default:
    return state;
  }
}

Some actions:

const push = value => new SmartAction(dispatch => {
  dispatch({type: 'PUSH', value});
});

const pop = () => new SmartAction((dispatch, getState) => {
  if (getState().length) {
    dispatch({type: 'POP'});
  }
});

And a store:

const createStoreWithMiddleware = applySmartMiddleware(
  smartActionMiddleware
)(createStore);

function createSmartStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

const store = createSmartStore();

Do something if an action executed:

if (store.dispach(pop()).exec()) {
  console.log('Pop executed!');
}

Do something if an action can execute but don't execute it:

if (store.dispach(pop()).canExec) {
  console.log('Pop can execute!');
}

Do something if an action can execute then execute it

const pop = store.dispatch(pop());

if (pop.canExec) {
  console.log('Pop can execute!');
  pop.exec();
  console.log('Pop executed!');
}

Execute an action if another didn't execute

// This will pop if it can, or push if it didn't pop
store.dispach(pop()).exec() || store.dispach(push(1)).exec();

Execute an action if another executed

// This will pop if it can, then push if it popped
store.dispach(pop()).exec() && store.dispach(push(1)).exec();

Compose actions

// This action will push all given arguments
const pushMutiple = (...values) => new SmartAction(dispatch => {
  values.forEach(v => dispatch(push(v)).exec());
});

store.dispatch(pushMultiple(1, 3, 4)).exec();
console.log(store.getState()); // [ 1, 2, 3 ]

Limitations

Deferring execution

If you choose to defer the execution of a SmartAction, be sure that the state hasn't change in the mean time.

For instance, DON'T DO THIS:

push(1).exec();
const deferPop = pop();
pop().exec();
deferPop.exec();

Async

Obviously, it is not possible to know if an action will dispatch in the future!

However, I have a feeling something can be done with promises. I'll get to it once that thought materializes...