Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle parent child data relationships in redux

Tags:

reactjs

redux

I'm building a simple todo app where a user can create projects and then add todo items. I believe my state should look something like this:

{
  projects: {
    1: {
      id: 1,
      title: "New Project",
      todos: [1, 2]
    }
  },
  todos: {
    1: {
      id: 1,
      text: "This is the first todo",
      isComplete: true,
      project: 1
    },
    2: {
      id: 2,
      text: "This is the second todo",
      isComplete: false,
      project: 1
    }
  }
}

When creating a new todo I need to update the todos state with the new todo and I need to update the parent project in the projects state.

What is the best way to handle this? Do both of the reducers need actions to handle this? Or is there someway the todos reducer can call an update action in the projects reducer?

EDIT: Here's how I changed the data structure to work better with redux

{
  projects: {
    condition: {
      currentProject: 1
    },
    entities: {
      1: {
        id: 1,
        title: "New Project"
      }
    }
  },
  todos: {
    condition: {
      currentFilter: 'SHOW_ALL'
    },
    entities: {
      1: {
        id: 1,
        text: "This is the first todo",
        isComplete: true,
        project: 1
      },
      2: {
        id: 2,
        text: "This is the second todo",
        isComplete: false,
        project: 1
      }
    }
  }
}

This way each of my reducers being combined at the root level are scoped to one root key. entities are persisted but condition is not. All other parts of state are computed use reselect. I'd be curious to see how others are solving similar problems in a front end only app.

like image 811
slightlytyler Avatar asked Oct 23 '15 22:10

slightlytyler


1 Answers

When projects reference their todos and vice versa, you're duplicating data. Unless you're working on an extremely large collection, I would recommend against that pattern. Instead, whenever you need a list of todos that match a project, filter the todos collection. Something like reselect is ideal for this.

This way when you dispatch CREATE_TODO, you only need the todos reducer to listen.

Keep your reducers decoupled.

  1. Dispatching actions in reducers is an anti-pattern. You can't do something in one reducer and pass that information to another directly. A better solution is

    dispatch({ type: CREATE_TODO, payload: { text: 'dsfdf' })
    dispatch({ type: ASSOCIATE_TODO_WITH_PROJECT, payload: { todo: todoid, project: 2 })
    

    but even that won't work because you don't know what todoid is. Which brings me to...

  2. You shouldn't have to know the id of the todo before you create it. In your reducer setup, you need to know the todo's id in order to add it to a project. That means you need to provide it in the action payload. But then you're view would have to generate the next id is, which is not ideal.

    Calling state.todos.filter(todo => todo.project == currentProject.id) in mapStateToProps is a better solution than figuring out what the next id should be in the view and passing it with the action.

    If this is done with a db, then you would use something like this with redux-thunk

    export function createTodo(text, project) {
      return function(dispatch) {
        dispatch({ type: CREATE_TODO_PENDING });
        fetch('/todos', { method: 'POST', body: { text: text, project: project } })
          .then(function(response) { // { id: 134452, text: text, project: project }
            dispatch({ type: CREATE_TODO_SUCCESS, payload: response })
          }.catch(err) { dispatch({ type: CREATE_TODO_FAIL }) }
      }
    }
    

    If you're using a backend that handles the id, then you can do the above and have both reducers listen to the events and assign todos to projects and projects to todos at the same time. But I still don't really recommend doing that.

  3. You risk having data out of sync because you lack a single source of truth. If your projects reducer says a project owns a todo with id === 3, but that todo says a different project owns it, who does that todo belong to? Obviously a bug has occured, but that could be prevented altogether by only having one child keep track of parent.

    Though, again, if you're using a database, then the database should keep those things synced up (still more surface area for bugs, but less likely). In which case as long as your client data always reflected server data, you should be fine.

like image 194
Nathan Hagen Avatar answered Sep 25 '22 12:09

Nathan Hagen