Redux - Removing object from array nested in anoth

2019-08-27 18:36发布

My Redux state stores a comments object which stores an array of comments. Each comment object has a replies array in it. I am passing both the parent comment ID and the reply ID to my reducer with the intention to remove the reply from the replies array.

A simplified version of my top-level comments object looks like this:

{
  "count": 4,
  "data": [
    {
      id: 123,
      text: 'This is a comment',
      replies: [
        {
          id: 479,
          text: 'This is a reply',
        },
        {
          id: 293,
          text: 'This is another reply',
        },
      ],
    },
    {
      id: 728,
      text: 'This is another comment',
      replies: [
        {
          id: 986,
          text: 'This is a reply',
        },
      ],
    },
  ],
  "pageSize": 5,
  cursor: "",
}

And here is my reducer which appears to wrap the parent comment object in an array and flatten the replies (obviously not the desired result but I am at a loss as to the best way to tackle the nested array).

case types.DELETE_REPLY_SUCCESS: {
  const content = Object.assign({}, state);
  content.data = content.data.map((comment) => {
    const newObj = { ...comment };
    if (newObj.id === action.parentCommentId) {
      return newObj.replies.filter(reply => reply.id !== action.replyId);
    }
    return newObj;
  });
  return content;
}

2条回答
来,给爷笑一个
2楼-- · 2019-08-27 19:19

The answer above is more direct but I wanted to share some of my thoughts.

First off, this link on normalizing state in Redux solved what was a big problem for me...dealing with nested data structures...your code becomes very complex. 90% of your reducers should be surprisingly simple. And with nested data structures they become complex.

Although normalizer is a pretty standard tool for normalizing state i'd recommend writing your own at first. I learned a lot from doing it that way.

What is a normalized Data Structure?

It's flat. And it's typically relational.

Dan Abramov (who built Redux), suggests storing pieces of data grouped. So your comments become one group and your replies share another. Just like in a relational Database. Each "group" has its own "table".

At first this seemed counter intuitive to me because it feels like you are writing more data structure. You're not... and the payoffs are well worth it.

So you would store your data something like this

{
  "comments": {
    "allIds" : [1,2,3,4],
    "byId": {
      1: {
        replies: [12],
        comment: "a comment"
      },
      2: {
        replies: [13],
        comment: "another comment"
      },
      3: {
        replies: [14],
        comment: "one more comment"
      },
      4: {
        replies: [15],
        comment: "oh look, a comment"
      },
    }
  },
  "replies" : {
    "allIds" : [12,13,14,15],
    "byId": {
      12: {
        comments: [1],
        reply: "a reply"
      },
      13: {
        comments: [2],
        reply: "another reply"
      },
      14: {
        comments: [3],
        reply: "yet another reply"
      },
      15: {
        comments: [4],
        reply: "a reply"
      }
  }
}

Why the big deal?

In what way does this make life easier? Well first off, if we ever want to display a list of comments, we can just map() through our allIds array and use the first parameter to access the key to get to the data.

This means we can iterate through a single array instead of through a nested object.

In the same way, and in answer to your question you can delete an element by using filter() instead of map(). I wont bother explaining filter here..it takes ten seconds to find examples that are better than anything I could explain.

Then just make sure you've followed the standard Redux way of doing things.

  1. Replicate your data structure using reducers. Your state should initialise with no data...that way you know you've done it right.

  2. Write your reducers that handle it's specific piece of state (comments reducer, etc) For me, these normally consist of switch statements that either return state or return a new state.

  3. Write actions that supply a reducer the new data it needs. Always follow the JS principle of having a function handle one job. Actions shouldn't do multiple things.

NOTE however using middleware like thunk, actions can dispatch other actions which can result in logic awesomeness!

a delete action for a comment might look something as simple as

function deleteComment(commentId) {
  return {
    type: "delete project",
    payload: commentId
  }
}

This gives us all we need. By using the previously mentioned switch statement in our reducer, we can check to see what type of action is being dispatched. (this tells us what to do with the supplied data) and which element to delete i.e the payload.

You can follow this simple approach for 85% of your app needs.

I understand there's a lot more to Redux than this but this approach really helped me get to grips with how to view Redux and how to manage and manipulate state. I HIGHLY recommend going through the whole eggheads.io tutorial by Dan Abramov linked above. He does a really great job of explaining pretty much everything in detail.

I hope this long answer to your short question helps.

查看更多
Rolldiameter
3楼-- · 2019-08-27 19:25

Updating a nested structure can be very nasty in redux. I suggest using a flatter structure. You can do this using normalizer or manually.

However, if you need to update your existing structure, these are the steps:

  1. Find the index of the comment you wish to change.
  2. Create a new array of filtered replies for the comment.
  3. Create a new data array by replacing the old comment with a new comment that contains the new replies array.
  4. Create a new state object with a new data array.

Example

const state = {"count":4,"data":[{"id":123,"text":"This is a comment","replies":[{"id":479,"text":"This is a reply"},{"id":293,"text":"This is another reply"}]},{"id":728,"text":"This is another comment","replies":[{"id":986,"text":"This is a reply"}]}],"pageSize":5,"cursor":""};

const action = {
  parentCommentId: 123,
  replyId: 479
};

const deleteReplySuccess = () => {
  const data = state.data;
  
  // find the index of the comment you want to update
  const commentToChangeIndex = data.findIndex(({ id }) => id === action.parentCommentId);
  
  // if it doesn't exist return the state
  if(commentToChangeIndex === -1) {
    return state;
  }
  
  // get the comment
  const comment = data[commentToChangeIndex];
  
  // create an updated comment with filtered replies array
  const updatedComment = {
    ... comment,
    replies: comment.replies.filter(reply => reply.id !== action.replyId)
  };

  // create a new state using object spread or assign
  return {
    ...state,
    data: [ // create a new data array
      ...data.slice(0, commentToChangeIndex), // the comments before the updated comment
      updatedComment, // the updated comment
      ...data.slice(commentToChangeIndex + 1) // the comments after the updated comment
    ]
  };
};

console.log(deleteReplySuccess());

查看更多
登录 后发表回答