unistore icon indicating copy to clipboard operation
unistore copied to clipboard

How to run an action from outside a connected component

Open codeincontext opened this issue 7 years ago • 5 comments

Similar to #75

You sometimes need to run an action from outside a connected component. I haven't seen a recommended pattern to do this so have come up with a temporary solution

This is basically mapActions but scoped to the current module's actions

export function bindActions(store) {
  const innerActions = actions(store);
  let mapped = {};
  for (let i in innerActions) {
    mapped[i] = store.action(innerActions[i]);
  }
  return mapped;
}

// usage
bindActions(store).setActiveOrder(order);

This also works, but uses a string to reference the action name

export const runAction = (store, actionName, args) => store.action(actions(store)[actionName])(args);

// usage
runAction(store, 'setActiveOrder', order);

Any thoughts on a nice way to do this?

codeincontext avatar Feb 20 '18 16:02 codeincontext

Agreed. I wish mapActions were exposed directly, but I did something like your first, but instead of current module's actions, taking both state and actions as parameters to make it reusable.

ehaynes99 avatar Apr 03 '18 18:04 ehaynes99

Might be worth taking a peek at Stockroom. It extends Unistore with this functionality in order to make actions centralized (since they're running in a single Web Worker).

The key is to replace store.action() with an implementation that pulls actions by name rather than expecting a mapping/mapping function.

developit avatar Apr 27 '18 02:04 developit

It seems like we have a fairly decent surface area for this already. Most notably, it's a bit hard to provide a simple API for bindActions, since it requires a Store reference due to the store not being a singleton.

I tend to use things like your example above, or just throw the .action() call inline:

import { add, subtract } from './actions';

store.setState({ value: 5 })
store.action(add)(5)
store.getState().value // 10

developit avatar May 21 '18 12:05 developit

The following is not really a 'pattern' as such, but it's certainly a simple way that's supported well by the API:

const store = createStore({
  lols: 5,
});

const actions = (store) => ({
  myCoolAction(state) {
    return { ...state, lols: state.lols+1 }
  },
});

store.setState(actions(store).myCoolAction(store.getState()));

Birch-san avatar Mar 14 '19 23:03 Birch-san

Here's the pattern I settled on:

const store = createStore({
  ready: false,
  lols: 5,
});

const actions = (store) => ({
  increment(state) {
    return {...state, lols: state.lols+1 }
  },
  setReady(state, ready) {
    return {...state, ready: ready, }
  },
});

function act(store, actionsObj, action, ...args) {
  return store.setState(
    actionsObj[action](
      store.getState(),
      ...args));
}
const actor = act.bind(null, store, actions(store));
const boundActions = Object.assign({}, 
  ...Object.keys(actions(store))
    .map(action => ({
      [action]: actor.bind(null, action),
    })),
);

boundActions.increment();
boundActions.setReady(true);

Birch-san avatar Mar 16 '19 19:03 Birch-san