josepot / rereducer

Create declarative redux reducers without boilerplate.
MIT License
13 stars 1 forks source link

v3 ideas #1

Closed josepot closed 6 years ago

josepot commented 6 years ago

I'm going to use the issue to try to spit out some thoughts that I've been having... Sorry if this looks more like a rant than an issue.

So, the reason I started rereducer is because I find the typical redux reducers quite verbose and imperative, and I wanted to be able to break them down into smaller functions and then "glue" them together with a reducer enhancer.

I thought that a basic way to split reducer functions was to separate the "case" from the "transformation". By "case" I mean checking whether the a new state has to be returned for the given action with the current state, and by "transformation" I mean the function that generates the new state. That's basically what rereducer helps us do.

But at the end of the day rereducer is just a reducer-enhancer and as such it can be composed and/or used with other enhancers.

Since I started using it I found myself creating other reducer-enhancers that I re-use pretty often. I also happen to use Ramda in almost all my projects. So, by combining ramda with rereducer and those enhancers, I was able to write my redux reducers as a composition of many small point-free functions. Which may look weird for those not use to that (everyone else in this planet), but it has some benefits... Like identifying common patterns, so that I can create new enhancers for those :see_no_evil: :joy: (Just kidding, I will explain those advantages in detail in a blog post). A silly example of what I'm talking about.

To the point: I'm considering re-purposing this library to make it become a collection of reducer-enhancers and helper-reducers.

It goes without saying that the current default function will still exist. What I'm not sure though, is whether in v3 we should still have a default function. I think that if rereducer becomes a collection of enhancers and helper-reducers, then perhaps the current default function should be called something like switchReducers or just switch?

We could also keep it as a default function and as a named function, but that's a bit weird.

These are some of the other enhancers that I would like to add:

The first parameter is the getter and it can take the following types of arguments:

const getId = (state, action) => action.payload.id;
const concatActionText = (state = '', action) => state + action.payload.text;
const appendText = subReducer([getId, 'text'], concatActionText);

appendText(
  {1: {id: 1, text: 'hello'}, 2: {id: 2, text: 'bye'}},
  {type: 'APPEND', payload: {id: 1, text: ' world'}}
); // => {1: {id: 1, text: 'hello world'}, 2: {id: 2, text: 'bye'}}

The second parameter is the transformation reducer, scoped to the value that's being transformed.

const getNextId = state => Object
  .keys(state)
  .map(parseInt)
  .reduce((acc, key) => key >= acc ? key + 1 : acc, 1);
const getPayloadText = (x, {payload: {text}}) => text;

const addItem = addReducer(getNewId, {
  id: getNewId,
  text: getPayloadText,
  completed: false,
});

addItem(
  {},
  {type: 'ADD_ITEM', text: 'hello world'}
); // => {1: {id: 1, text: 'hello world'}} 

The first parameter works the same as the first parameter of subReducer. The first parameter is a reducer that's a getter for the key of the value that will be assigned to the current state, just for consistency with the rest of the API it can also be a static value. The second parameter can be:

Ok, yes, addReducer is actually just "sugar" for "subReducer"... So, perhaps the 2 of them could be merged into 1. Meaning that we could get rid of addReducer, and just make it an special case for when the second parameter of subReducer receives an object or a primitive instead of a function... It's an option, and perhaps it's the best one. The only thing that makes me hesitant about that is the crazy amount of overloads that enhancer will end up having... But I think it's reasonable.

It is not true at all that addReducer is just sugar for subReducer, that was a brain-fart.

const getIdFromAction = (x, {payload: {id}}) => id;
const removeItem = deleteReducer(getIdFromAction);
removeItem(
  {1: {id: 1, text: 'hello world'}, 2: {id: 2, text: 'bye'}},
  {type: 'DELETE', payload: {id: 2}}
); // =>  {1: {id: 1, text: 'hello world'}}

Other enhancers that I would like to add, but that I won't explain into detail now (although you can get an idea), are:

Finally, I would also like to add 2 helper reducers:

const getPayloadText = payload('text'); getPayloadText(null, {type: 'FOO', payload: {text: 'fooText'}}); // => 'fooText'

const getPayloadFooId = payload('foo', 'id'); getPayloadFooId(null, {type: 'FOO', payload: {foo: {id: 'fooId'}}}); // => 'fooId'



I would like to know what @voliva thoughts are around all these things, summarizing:

- Do you think that's a good idea to re-purpose this library to make it a library for reducer helpers/enhancers? Should these things go into a different library/ies?
- What do you think about the enhancers that I'm suggesting and their APIs, does it make sense? Do you think that we would be able to get decent TS typings?
- Should we keep the default export?

(If you actually read this far... Well, thanks and sorry! :sweat_smile: )
voliva commented 6 years ago

Hey @josepot, sorry for taking a bit long having a look at this, here are my thoughts

First, answering your specific questions:

Now, to other points I wanted to mention: