Search code examples
reactjstypescriptreduxredux-toolkit

React Redux Toolkit access value inside array of objects


Component:

const postState = useSelector((state: RootState) => state.postsSlice.posts);

{
  postState?.map((post) => {
    return (
      <PostCard
        key={post.id}
        title={post.title}
        description={post.body}
        user={post.user}
        voteCount={post.upvotes - post.downvotes}
        onClick={() => navigate(`/posts/${subredditId}/post/${post.id}`)}
      />
    );
  });
}

I am working on a reddit clone project with react typescript. I take the data from the api as an array of objects of the type:

export type Posts = Post[];

export type Post = {
  id: string;
  title: string;
  user: string;
  body: string;
  upvotes: number;
  downvotes: number;
};

And then I store the data in redux toolkit like this:

type PostsArray = {
  posts: Array<Post>;
};

const initialState: PostsArray = { posts: [] };

const PostsSlice = createSlice({
  name: "PostsSlice",
  initialState: initialState,
  reducers: {
    setPostsData(state, action: PayloadAction<Array<Post>>) {
      state.posts = action.payload;
    },
  },
});
export const { setPostsData } = PostsSlice.actions;
export default PostsSlice.reducer;

when I map the objects in my component I show the reddit vote count by subtracting downvotes from upvotes. What I want to do is being able to upvote and downvote different posts and store that vote in redux. I thought about a dispatch that increase the upvotes and a dispatch that increase the downvotes. But I cant figure it out how to do it. I dont know how to acces the upvotes of a specific post in redux. Any ideas?


Solution

  • For such case you can add a reducer that gets the post id and updates it with the value passed on the action payload, like so:

    Reducer

    const PostSlice = createSlice({
      name: "PostSlice",
      initialState: initialState,
      reducers: {
        setPostsData(state, action: PayloadAction<Array<Post>>) {
          state.posts = action.payload;
        },
        // reducer to change the vote count
        updateVoteCount(
          state,
          // in the vote payload you can use an enumerate 
         // if you want to avoid typos
          action: PayloadAction<{ id: string; vote: string }>
        ) {
          // find the post by id
          const post = state.posts.find((post) => post.id === action.payload.id);
          if (!post) return;
          if (action.payload.vote === "up") {
            // update the vote depending on payload vote parameter
            post.upvotes += 1;
          }
          if (action.payload.vote === "down") {
            post.downvotes += 1;
          }
        }
      }
    });
    

    PostCard

    As you didn't provide the code of your PostCard I make a bare example just to show to you:

    const PostCard = (props: PostCardProps) => {
      const dispatch = useDispatch();
      return (
        <div>
          <h2>{props.title}</h2>
          <p>{props.description}</p>
          <p>{props.user}</p>
          <p>{props.voteCount}</p>
          <div>
            <button
              onClick={() =>
                //Dispatch the action with vote "up"
                dispatch(updateVoteCount({ id: props.id, vote: "up" }))
              }
            >
              Up vote
            </button>
            <button
              onClick={() =>
                // dispatch with vote "down"
                dispatch(updateVoteCount({ id: props.id, vote: "down" }))
              }
            >
              Down vote
            </button>
          </div>
        </div>
      );
    };
    export default PostCard;
    

    Please, check the working example.