pauldijou / redux-act

An opinionated lib to create actions and reducers for Redux

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Consider supporting multiple actions in `createReducer` shorthand

SaladFork opened this issue · comments

I'm converting my code from plain redux reducers to ones using redux-act. I've been really liking it so far but there's one case that isn't quite handled.

Today I have reducers similar to (simplified example below):

export default function loadingReducer(state, action) {
  switch (action) {
    case 'SOME_FETCH_START':
      return { ...state, [action.id]: true }
    case 'SOME_FETCH_SUCCESS':
    case 'SOME_FETCH_FAILURE':
      return omit(state, action.id)
    default:
      return state;
  }
}

It is somewhat common that my reducers handle multiple actions with the same state mutation behavior. This is even more common given that I normalize my data with normalizr so there is a lot of commonality between requests.

This is somewhat straightforward to turn into redux-act shorthand:

export default createReducer({
  [someFetchStart]: (state, id) => ({ ...state, [id]: true }),
  [someFetchSuccess]: (state, id) => omit(state, id),
  [someFetchFailure]: (state, id) => omit(state, id)
})

(much nicer! :D)

I was hoping to combine the latter two into a single case, similar to how I handle it in the original switch statement. Searching the docs and issues on this project, I found #18 and #54 and saw that support was added to on to accept multiple actions for a single handler. I could not figure out how to get this to work with the shorthand approach, though.

Ideally, I'd be able to do:

export default createReducer({
  [someFetchStart]: (state, id) => ({ ...state, [id]: true }),
  [someFetchSuccess, someFetchFailure]: (state, id) => omit(state, id)
})

This is, of course, a JavaScript syntax error as [] here is used as a way to denote a dynamic object key rather than an array. I next tried:

export default createReducer({
  [someFetchStart]: (state, id) => ({ ...state, [id]: true }),
  [[someFetchSuccess, someFetchFailure]]: (state, id) => omit(state, id)
})

which did not error out, but also did not work at all. Would it be possible to add support for such? I realize the workaround described in #18 still works here (extract behavior to a separate function, call function from both cases), but a lot of my cases are very simple (as per above) and extracting them seems unnecessary. I think it also loses some of the semantics of the switch statement of "handle these actions together".

#54 mentions and version 1.4.0 added this ability to on, but I could not find an equivalent for the object-based approach.

As a comparison point, redux-action seems to offer a combineActions to accomplish this.

I don't think it would work with a raw array because you can't really override its toString method (which is the one used with dynamic keys). So best case scenario would be:

export default createReducer({
  [someFetchStart]: (state, id) => ({ ...state, [id]: true }),
  [combineActions(someFetchSuccess, someFetchFailure)]: (state, id) => omit(state, id)
})

But then, toString can only return one string, so you would need a specific syntax to merge each action type. Like:

function combineActions(...actions) {
	return {toString: () => actions.map(a => a.toString()).join('__$$__') };
}

Which is exactly what redux-action does btw. And then, normalise such syntax inside the reducer:

function createReducer(handlers = {}) {
	Object.keys(handlers).forEach(k => {
		const types = k.split('__$$__');
		types.forEach(t => {
			handlers[t] = handlers[k];
		});
	})
}

Which is not awesomely performant but createReducer should not be called that often in your app. But it rises another issue: what if you do:

createReducer({
	[someFetchStart]: (state, id) => ({ ...state, [id]: true }),
 	[combineActions(someFetchStart, someFetchFailure)]: (state, id) => omit(state, id)
})

The 2nd someFetchStart will actually override the 1st one because one reducer only has one reduce function for a given action. It's not that obvious from reading the code. One would expect both functions to be applied.

We could allow multiple functions per action but I don't really want to because it adds complexity to your code. Suddenly, you have to track potentially several places where your action could have impact inside your reducer.

Long story short, right now, I think allowing such pattern would create more issues than it would solve. The createReducer with the on function is a valid syntax for such use-case.