[Solved] Organizing reducers in Redux

Derek 朕會功夫 Asks: Organizing reducers in Redux
I will use the following popular todo list example for demonstration. Let’s say I wanted to create a very basic todo list with this structure as the app state:

Code:
{
    todos: {
        todoKey1: {
            content: "todoKey1 content",
            ...
        },
        todoKey2: ...
    }
}

Because of the recursive nature of reducers, we can separate out the reducers into different files:

Code:
// reducers/app.js:
import todos from "./todos";
export default function app(prev={}, action){
    return {
        todos: todos(prev.todos, action)
    };
}

// reducers/todos.js:
import todo from "./todo";
export default function todos(prev={}, action){
    switch(action.type){
        case "ADD_TODO":
            return {
                ...prev,
                [action.id]: todo(undefined, action)
            };
        default:
            return prev;
    }
}

// reducers/todo.js:
export default function todo(prev={}, action){
    switch(action.type){
        case "ADD_TODO":
            return {content: action.content};
        default:
            return prev;
    }
}

Everything looks good so far – nice and organized. This is where my question comes in: I don’t want to include the new key for the new todo in the action. Instead I want the action to be very pure, in which it should describe only the action (adding a new todo item). The id is irrelevant.

I would like the key to be automatically generated whenever I add a todo item and thus I have modified the state structure to include next_available_id which indicates what the key should the next todo item be using:

Code:
{
    next_available_id: 0,
    todos: {
        todoKey1: {
            content: "todoKey1 content",
            ...
        },
        todoKey2: ...
    }
}

This breaks the nice recursive reducers that I had since now it todos() doesn’t have the id to create the new entry and the action does not have the id. Instead, I will have to combine the reducer in todos.js into app.js:

Code:
// reducers/app.js:
import todo from "./todo";
export default function app(prev={}, action){
    switch(action.type){
        case "ADD_TODO":
            let todos = prev.todos || {};
            return {
                ...prev,
                next_available_id: prev.next_available_id + 1,
                todos: {
                    ...todos,
                    [prev.next_available_id]: todo(undefined, action)
                }
            };
        default:
            return prev;
    }
}

Possible solutions that I can think of:

  • Dispatch multiple actions: one for incrementing next_available_id and another one for the actual todo creation.
    • Pro: app.js and todos.js can remain separated
    • Con: This can get really messy in the actual application as there would be a lot of extra actions
  • Modify the action itself before passing into todos.
    • Pro: Eliminates the need for an additional action
    • Con: Actions should be immutable

I feel like I’m missing something here. What would be the standard way of handling situations like this?

Ten-tools.com may not be responsible for the answers or solutions given to any question asked by the users. All Answers or responses are user generated answers and we do not have proof of its validity or correctness. Please vote for the answer that helped you in order to help others find out which is the most helpful answer. Questions labeled as solved may be solved or may not be solved depending on the type of question and the date posted for some posts may be scheduled to be deleted periodically. Do not hesitate to share your response here to help other visitors like you. Thank you, Ten-tools.