Как правильно прописать extraReducer в постах, который следит за добавлением или удалением лайка

Есть такой массив постов, пытаюсь прописать экстраредюсер, следящий за лайками, но никак не получается сделать это правильно. Всё, чего смог достичь это либо при лайке перезагружается весь компонент с постами, либо (текущий вариант) при лайке визуально увеличивается количество лайков на всех постах, работает 2 клика подряд потом ничего не меняет.

На беке на данный момент одна функция, которая проверяет,существует ли лайк или нет, и создаёт его или удаляет в зависимости от проверки. Лайки связаны с постами и юзерами через связь в секвалайзе belongsToMany

https://i.stack.imgur.com/rln66.png

likeslice


const initialState = {
  likes: []
}

export const upvotePost = createAsyncThunk(
  'likes/upvotePost',
  async (id, { rejectWithValue, dispatch }) => {
    try {
      const response = await axios.post(`/posts/${id}/likes`);
      dispatch(addLike(response.data.like))
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

// export const unlikePost = createAsyncThunk(
//   'likes/unlikePost',
//   async (id, { rejectWithValue, dispatch }) => {
//     try {
//       await axios.delete(`/posts/${id}/likes`);
//       dispatch(unlike(id))
//     } catch (error) {
//       rejectWithValue(error.message);
//     }
//   }
// )

export const getPostLikes = createAsyncThunk(
  'likes/getPostLikes',
  async(id, {rejectWithValue, dispatch}) => {
    try {
      const response = await axios(`/posts/${id}/likes`);
      dispatch(getLikes(response.data.postLikes));
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

export const likeSlice = createSlice({
  name: 'likes',
  initialState,
  reducers: {
    addLike: (state, action) => {
      state.likes.push(action.payload);
    },
    // unlike: (state, action) => {
    //   state.likes = state.likes.filter(el => el.id !== action.payload)
    // },
    getLikes: (state, action) => {
      state.likes = action.payload;
    }
  }
})

export const { addLike, unlike, getLikes } = likeSlice.actions;
export default likeSlice.reducer;

postSlice

const initialState = {
  posts: [],
  post_id: {}
}

export const addNewPost = createAsyncThunk(
  'posts/addNewPost',
  async (value, { rejectWithValue, dispatch }) => {
    try {
      const response = await axios.post('/posts', value);
      dispatch(addPost(response.data.newPost));
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

export const getAllPosts = createAsyncThunk(
  'posts/getAllPosts',
  async (_, { rejectWithValue, dispatch }) => {
    try {
      const response = await axios('/posts');
      dispatch(getPosts(response.data.allPosts));
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

export const removePost = createAsyncThunk(
  'posts/removePost',
  async (id, { rejectWithValue, dispatch }) => {
    try {
      await axios.delete(`/posts/${id}`);
      dispatch(deletePost(id));
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

export const getOnePost = createAsyncThunk(
  'posts/getOnePost',
  async (id, { rejectWithValue, dispatch }) => {
    try {
      const response = await axios(`/posts/${id}`);
      const result = {
        ...response.data.onePost,
        userId: response.data.onePost.User.id,
        userName: response.data.onePost.User.name,
        userEmail: response.data.onePost.User.email,
        userInfo: response.data.onePost.User.info,
        userAvatar: response.data.onePost.User.avatar
      }
      dispatch(getPostById(result));
    } catch (error) {
      rejectWithValue(error.message);
    }
  }
)

export const postSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    addPost: (state, action) => {
      state.posts.push(action.payload);
    },
    getPosts: (state, action) => {
      state.posts = action.payload;
    },
    deletePost: (state, action) => {
      state.posts = state.posts.filter(el => el.id !== action.payload);
    },
    getPostById: (state, action) => {
      state.post_id = action.payload
    }
  },
  extraReducers: (builder) => {
    builder.addCase(likeSlice.actions.addLike, (state, action) => {
      console.log('extra reducer LIKE payload', action.payload);
      state.posts = state.posts.map(post => {
        if (post.likedBy.map(user => user.Likes !== action.payload)) {
          post.likedBy.push(action.payload)
          return {...post }
        } else {
          return post
        }
      })
    })
  //   builder.addCase(likeSlice.actions.unlike, (state, action) => {
  //     console.log('extra reducer UNLIKE', 'payload', action.payload);
  //     state.post_id = action.payload 
  //     state.posts = state.posts.filter(el => el.id !== action.payload)
  //   })
  },
})

const { addPost, getPosts, deletePost, getPostById } = postSlice.actions;
export default postSlice.reducer;

payload выглядит так

extra reducer LIKE payload 

{post_id: 51, user_id: 10, updatedAt: '2023-06-09T12:43:25.871Z', createdAt: '2023-06-09T12:43:25.871Z'}

Ответы (1 шт):

Автор решения: Aleksandr

На основе предоставленного кода, я вижу, что ты пытаешься обновить состояние posts в extraReducers раздела postSlice. Однако в твоем коде не реализована логика удаления лайка. Для правильного обновления состояния likedBy в постах тебе нужно реализовать обработку и добавление/удаление лайков в extraReducers. Вот как это можно сделать:

import { likeSlice } from './likeSlice';
//...

export const postSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    //...
  },
  extraReducers: (builder) => {
    builder
      .addCase(likeSlice.actions.addLike, (state, action) => {
        const { post_id, user_id } = action.payload;
        state.posts = state.posts.map(post => {
          if (post.id === post_id) {
            // Проверяем, есть ли уже лайк от пользователя
            const existingLike = post.likedBy.find(like => like.user_id === user_id);
            if (!existingLike) {
              post.likedBy.push(action.payload);
            }
          }
          return post;
        });
      })
      .addCase(likeSlice.actions.unlike, (state, action) => {
        const { post_id, user_id } = action.payload;
        state.posts = state.posts.map(post => {
          if (post.id === post_id) {
            // Удаляем лайк пользователя
            post.likedBy = post.likedBy.filter(like => like.user_id !== user_id);
          }
          return post;
        });
      });
  },
});

//...

В этом коде, при вызове likeSlice.actions.addLike, мы обновляем массив likedBy в соответствующем посте только в том случае, если лайк от данного пользователя еще не существует. Аналогично, при вызове likeSlice.actions.unlike, мы удаляем лайк пользователя из массива likedBy в соответствующем посте.

Обрати внимание, что тебе также понадобится раскомментировать соответствующий extraReducers для действия unlike в likeSlice.

Теперь, при вызове likeSlice.actions.addLike или likeSlice.actions.unlike, состояние posts должно обновляться корректно, и ты сможешь отслеживать и добавлять/удалять лайки на соответствующих постах.

→ Ссылка