<commit_before># combineReducers

Rematch and more generally Redux, encourage to keep a flat state to solve several [issues](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape).
Each model should be considered as a separate entity and should only be updated by its reducer, this is why Rematch doesn't allow nested reducers.

This being said, there is some case where nest reducer can be a good thing.

Let's start with an example. A common pattern in Redux when we want to store an ordered list: "allIds, byId". Store entities as
an array require to iterate over the array to find our target and store the entities as an object doesn't let us to keep 
an order.

```javascript
const todoList = {
  state: {
    byId: {
      0: {
        task: 'Learn Rematch',
        isDone: true
      },
      1: {
        task: 'Learn functional programming',
        isDone: false
      }
    },
    allIds: [1, 0]
  }
}
```

`combineReducers` would be helpful here because:
  - byId and allIds refers to the same model, they are the same entity
  - they don't depend to each other, so they would be handled separately
  
 ## Example without separating byId and allIds
 
 ```javascript
const todoList = {
  state: {...},
  reducer: {
    // remove update allIds and byId, but independately
    remove(state, payload) {
      const { idToRemove } = payload;
      return {
        byId: Object.entries(state.byId)
          .filter(([id, todo]) => id !== idToRemove)
          .reduce((acc, [id, todo]) => ({ ...acc, [id]: todo }), {}),
        allIds: state.allIds.filter(id => id !== idToRemove)
      };
    },
    // update only update our byId key
    toggle(state, payload) {
      const { idToToggle } = payload;
      return {
        ...state, // don't update allIds,
        byId: Object.entries(state.byId)
          .map(([id, todo]) => [
            id,
            id === idToToggle ? { ...todo, isDone: !todo.isDone } : todo
          ])
          .reduce((acc, [id, todo]) => ({ ...acc, [id]: todo }), {})
      };
    }
  }
};

```
Our reducer is doing too much here.
We can separate our reducer easily by creating some update function.

Start decoupling the reducers with reusable functions:

```javascript
function filterObjectByKey(obj, f) {
  return Object.entries(obj)
    .filter(([key, value]) => f(key))
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
}

function updateObject(obj, f) {
  return Object.entries(obj)
    .map(([key, value]) => [key, f(key, value)])
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
}
```

Let's rewrite it!

```javascript
function removeById(state, payload) {
  const { idToRemove } = payload;
  return filterObjectById(state, id => id !== idToRemove);
}

function removeAllIds(state, payload) {
  const { idToRemove } = payload;
  return state.filter(id => id !== idToRemove);
}

function toggleById(state, payload) {
  const { idToToggle } = payload;
  return updateObject(
    state,
    (id, todo) => (id === idToToggle ? { ...todo, isDone: !todo.isDone } : todo)
  );
}

function toggleAllIds(state, payload) {
  return state;
}
```

Now we can use:

```javascript
const todoList = {
  state: {...},
  reducer: {
    // remove update allIds and byId, but independately
    remove(state, payload) {
      return {
        byId: removeById(state.byId, payload),
        allIds: removeAllIds(state.allIds, payload)
      };
    },
    // update only update our byId key
    toggle(state, payload) {
      return {
        byId: toggleById(state.byId, payload),
        allIds: toogleAllIds(state.allIds, payload)
      };
    }
  }
};

```




 <commit_msg>Update combineReducers.md<commit_after># Decoupling reducers

Rematch and more generally Redux, encourage to keep a flat state to solve several [issues](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape).
Each model should be considered as a separate entity and should only be updated by its reducer, this is why Rematch doesn't allow nested reducers.

This being said, there is some case where nested reducer can be a good thing.
In this recipe, we will see how to decoupling reducers that tends to grow too much.

Let's start with an example. A common pattern in Redux, when we want to store an ordered list, is "allIds, byId". Store  the entities as
an array requires to iterate over the array to find our target but store the entities as an object doesn't let us to keep 
an order. So let's use both: 

```javascript
const todoList = {
  state: {
    byId: {
      0: {
        task: 'Learn Rematch',
        isDone: true
      },
      1: {
        task: 'Learn functional programming',
        isDone: false
      }
    },
    allIds: [1, 0]
  }
}
```
`byId` key store the entity while `allIds` keeps the track of the order of our entities.

This driven us into an edge case: 
  - `byId` and `allIds` refers to the same entity, they definitely need to live into the same model
  - `byId` and `allIds` are don't depend on each other, they definitely need to be handled separately
 

Let's try to see what we get if we use the same model:
 
 ```javascript
const todoList = {
  state: {...},
  reducer: {
    // update allIds and byId
    remove(state, payload) {
      const { idToRemove } = payload;
      return {
        byId: Object.entries(state.byId)
          .filter(([id, todo]) => id !== idToRemove)
          .reduce((acc, [id, todo]) => ({ ...acc, [id]: todo }), {}),
        allIds: state.allIds.filter(id => id !== idToRemove)
      };
    },
    // update byId only
    toggle(state, payload) {
      const { idToToggle } = payload;
      return {
        ...state, // don't update allIds,
        byId: Object.entries(state.byId)
          .map(([id, todo]) => [
            id,
            id === idToToggle ? { ...todo, isDone: !todo.isDone } : todo
          ])
          .reduce((acc, [id, todo]) => ({ ...acc, [id]: todo }), {})
      };
    }
  }
};

```

We see that our reducers start to be big and pretty unreadable.
Hopefully, we can separate our update function.

We can start to isolate our pure reusable functions

```javascript
function filterObjectByKey(obj, f) {
  return Object.entries(obj)
    .filter(([key, value]) => f(key))
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
}

function updateObject(obj, f) {
  return Object.entries(obj)
    .map(([key, value]) => [key, f(key, value)])
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
}
```

Now we can separate our `update functions`, the functions that update a part of the state.

```javascript
function removeById(state, payload) {
  const { idToRemove } = payload;
  return filterObjectById(state, id => id !== idToRemove);
}

function removeAllIds(state, payload) {
  const { idToRemove } = payload;
  return state.filter(id => id !== idToRemove);
}

function toggleById(state, payload) {
  const { idToToggle } = payload;
  return updateObject(
    state,
    (id, todo) => (id === idToToggle ? { ...todo, isDone: !todo.isDone } : todo)
  );
}

function toggleAllIds(state, payload) {
  return state;
}
```

And we finally mix all together by distibuting a part of the state to our update functions:

```javascript
const todoList = {
  state: {...},
  reducer: {
    remove(state, payload) {
      return {
        byId: removeById(state.byId, payload),
        allIds: removeAllIds(state.allIds, payload)
      };
    },
    toggle(state, payload) {
      return {
        byId: toggleById(state.byId, payload),
        allIds: toogleAllIds(state.allIds, payload)
      };
    }
  }
};

```
Our model seems much better and more readable.



 
{'fraction_non_alphanumeric': 0.13348976665363055, 'fraction_numerical': 0.0010428888019814887, 'mean_word_length': 2.777449532250123, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '947950', 'n_tokens_mistral': 2417, 'n_tokens_neox': 2357, 'n_words': 822}