pauldijou / redux-act

An opinionated lib to create actions and reducers for Redux

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

'Or' in createReducer?

justrag opened this issue · comments

Is there a way to specify multiple action creator triggers for one function in createReducer?

createReducer({
[actionOne] OR [actionTwo]: (state, payload) => doSomething(state, payload),
}, defaultState)

... or do I have to repeat code for every one of them?

createReducer({
[actionOne]: (state, payload) => doSomething(state, payload),
[actionTwo]: (state, payload) => doSomething(state, payload),
}, defaultState)

You get this kind of behavior easily with fallthrough for the standard switch-based reducer...

Currently, there is no way to do that so you will have to repeat but you can create helpers to do so.

// Basic way
createReducer({
  [actionOne]: doSomething,
  [actionTwo]: doSomething,
}, defaultState)

// Helper
function onAll(reducer, handler, actions) {
  actions.forEach(action => reducer.on(action, handler));
}

const reducer = createReducer({}, defaultState);
onAll(reducer, doSomething, [actionOne, actionTwo]);
commented

Hey @pauldijou, I wrote this small helper for the object pattern which has kept our code tidy:

const allOf = (actions, reducer) =>
  actions.reduce((dict, action) => {
    dict[action] = reducer;
    return dict;
  }, {});

// done explicitly
createReducer(
  {
    [actionOne]: doSomething,
    [actionTwo]: doSomething,
    [actionThree]: doSomething,
    [actionFour]: doSomethingB,
    [actionFive]: doSomethingC
  },
  {}
);

// done with allOf
createReducer(
  {
    ...allOf([actionOne, actionTwo, actionThree], doSomething),
    [actionFour]: doSomethingB,
    [actionFive]: doSomethingC
  },
  {}
);

This would be useful to have on the API as fallthroughs are a common need - combineActions exists for this case in redux-actions. I'd be happy to refactor this to suit your code style and create a pull request?

Cheers for the awesome library!

@chacestew I'm fine adding this piece of code. The hardest part will probably be the name of the function 😄 Maybe an array function name like every or forEach. Or your name but I cannot figure if it makes more sense to have allOf or anyOf actually, not being a native English speaker does not help. combineActions is out of question, we are not combining anything.

That said, could anyone provide me a real world use-case for this feature? It does not make much sense to me. Why not have only one action which doSomething and use it, eventually batching it with others? Why would several different actions do the exact same thing without any slight difference?

commented

In our case, we are using normalizr on our API responses to collect data of a single type in one entities reducer with many "page" reducers to keep track of the entities to display. We need separate actions for each page reducer to add the needed results e.g. [...IDs]. The entities reducer takes all of those actions to store the entities themselves e.g. {[ID]: {data}, ...}.

A real world example is a job site where you can browse jobs in an infinite list or on company pages. So we don't duplicate the same jobs in state, there is a single reducer that holds all the jobs and it needs to handle any action that leads to jobs being loaded.

// entities reducer
const allJobs = createReducer(
  {
    [loadedNextJobsInList]: (state, payload) => ({ ...state, ...payload.entities }),
    [loadedJobsByCompany]: (state, payload) => ({ ...state, ...payload.entities })
    // ... etc.
  },
  {}
);

// job page reducer
const companyPage = createReducer(
  {
    [loadedJobsByCompany]: (state, payload) => ({ loading: false, results: payload.results })
    // ... etc.
  },
  {
    loading: false,
    results: []
  }
);

// infinite scroller dispatches something like this
loadNextJobsInList({ page: this.state.currentPage + 1 });

// company page dispatches this
loadJobsByCompany({ company: this.props.companyId });

We had an approach like this before adding redux-act, so I haven't looked at using the batch method or any other yet. Is there a better way of doing this with the existing API you can think of? It would make sense to not add a competing method if that's the case.

commented

Regarding the name, forEach describes it well and is a familiar term, however I don't know what the pros or cons are of mixing with the native API. I used allOf because it simply returns all of those mappings - anyOf is about the same but not 100% descriptive of the final shape of the reducer. each is another simple option, but we could go on... 😄

Yeah, I would probably use another way. I would use dedicated actions for the allJobs reducer and batch them with other actions. You avoid coupling between reducer and payload schemas.

Also, I find it more readable: each action has the "lowest" possible impact, meaning it's super easy to trace what each of them is doing, and when you dispatch a batch of them, it's straightforward to see which reducers will be impacted (assuming an action only impact one reducer). This is compared to some kind of a "meta" action impacting several reducers here and there.

// entities reducer
const allJobs = createReducer(
  {
    [addJobs]: (state, payload) => ({ ...state, ...payload}),
  },
  {}
);

// job page reducer
const companyPage = createReducer(
  {
    [loadedJobsByCompany]: (state, payload) => ({ loading: false, results: payload.results })
    // ... etc.
  },
  {
    loading: false,
    results: []
  }
);

// now, when you company page fetch its data,
// let's say it will be an object with the `results` field with entities
dispatch(batch(addJobs(data.results), loadedJobsByCompany(data)))
commented

I've switched over to using the batch method completely now and it's working well. That's a reminder to always explore the whole API. Thanks for your help!

Awesome! I will go back to stand-by mode until someone ask again with another real world use-case.