Search code examples
reactjsreduxnormalization

Best practice for deleting related entities in normalized state in Redux


When deleting an entity from normalized data, how do we deal with deleting other entities that are owned by the deleted entity? For example, for the following normalized data, if I'm going to delete user1, I would also like to delete all posts and comments made by user1. Are there any known how-tos or best practices for this situation?

{
    posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]    
            }
        },
        allIds : ["post1"]
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                author : "user1",
                comment : ".....",
            },
            "comment2" : {
                id : "comment2",
                author : "user1",
                comment : ".....",
            },
        },
        allIds : ["comment1", "comment2"]
    },
    users : {
        byId : {
            "user1" : {
                username : "user1",
                name : "User 1",
            }
        },
        allIds : ["user1"]
    }
}

Solution

  • There are a number of ways you can look at this:

    1. The reducers for each element are responsible for cleaning up the data for any action which deletes a user, or;
    2. The action of deleting a user has the side effect of deleting a number of associated items (or a number of associated actions are dispatched)

    Option 1

    Let's say you have an action like the following:

      const deleteUser = userId => {
        return ({
          type: 'DELETE_USER',
          userId
        })
      }
    

    Your reducer for user might look like this:

      const users = (state = {}, action) => {
    
        switch (action.type) {
          case 'DELETE_USER':
            // delete user logic
            break;
        }
    
      }
    

    Well there's technically nothing in Redux that stops you from reacting to the DELETE_USER action in the posts or comments reducers:

      const posts = (state = {}, action) => {
        const newState = Object.assign({}, state);
        switch (action.type) {
          case 'DELETE_USER':
            // delete posts for action.userId
            break;
        }
      }
    

    Option 2

    If you don't like the above, and want to keep some degree of separation of concerns, then consider looking at a way to trigger side-effects associated with an action, such as redux-saga or redux-thunk

    The implementation will vary depending on the library, but the idea is:

    1. Listen for DELETE_USER action
    2. Trigger some actions to:
      1. Delete a user (DELETE_USER)
      2. Delete posts for a user (DELETE_USER_POSTS)
      3. Delete comments for a user (DELETE_USER_COMMENTS)