rtk-incubator / rtk-query

Data fetching and caching addon for Redux Toolkit
https://redux-toolkit.js.org/rtk-query/overview
MIT License
626 stars 31 forks source link

What is the best way to make this work? #185

Closed fera765 closed 3 years ago

fera765 commented 3 years ago

Good morning, sorry to come back here. Based on the answer I got on another question, I am trying to make this implementation to work with my api;

Looking at good development practices, and also looking at the RTK-Query (library) development rule, is this code okay? I'm trying to do the manipulation without having to make a new api call, you as creators of the library what did you think of this code of mine, and if there is any better way to reproduce the same result as in the post without having to invalidate and have a new one call to API ...

I like to ask a lot of the creators of a library, because they know how to do it in the best possible way

import { emptySplitApi } from '.';
import { homePostApi } from './homePost';

//I removed some parts of the code

export const profileApi = emptySplitApi.injectEndpoints({
  endpoints: build => ({
    getProfile: build.query<IProfileResponse, string>({
      query: user_id => `profile?user_id=${user_id}`,
      provides: (_, user_id) => [{ type: 'Profile', id: user_id }],
    }),
    getProfilePosts: build.query<IPostsResponse, string>({
      query: username => ({
        url: `post/list?user=${username}&page=1&per_page=20`,
      }),
      provides: ({ data }) => [
        ...data.map(({ id }) => ({ type: 'Profile', id } as const)),
        { type: 'Profile', id: 'LIST' },
      ],
    }),
    getProfilePost: build.query<IPost, string>({
      query: id => `post/view/${id}`,
      provides: (_, id) => [{ type: 'Profile', id }],
    }),
    createLikeProfilePost: build.mutation<
      ILike,
      { username: string; post_id: string }
    >({
      query({ post_id }) {
        return {
          url: `likes`,
          method: 'POST',
          body: { post_id },
        };
      },

      onStart({ username, post_id }, { dispatch, context }) {
        context.undoPost = dispatch(
          profileApi.util.updateQueryResult(
            'getProfilePost',
            post_id,
            draft => {
              draft.count_likes += 1;
              draft.is_like = true;
            },
          ),
        ).inversePatches;

        context.undoPosts = dispatch(
          profileApi.util.updateQueryResult(
            'getProfilePosts',
            username,
            draft => {
              const postIndex = draft.data.findIndex(
                post => post.id === String(post_id),
              );
              if (postIndex !== -1) {
                draft.data[postIndex].is_like = true;
                draft.data[postIndex].count_likes += 1;
              }
            },
          ),
        ).inversePatches;
        context.undoPost = dispatch(
          homePostApi.util.updateQueryResult('getPost', post_id, draft => {
            draft.count_likes += 1;
            draft.is_like = true;
          }),
        ).inversePatches;

        context.undoPosts = dispatch(
          homePostApi.util.updateQueryResult('getPosts', undefined, draft => {
            const postIndex = draft.data.findIndex(
              post => post.id === String(post_id),
            );
            if (postIndex !== -1) {
              draft.data[postIndex].is_like = true;
              draft.data[postIndex].count_likes += 1;
            }
          }),
        ).inversePatches;
      },
      onError({ username, post_id }, { dispatch, context }) {
        console.log('rolling back', username, context);
        dispatch(
          profileApi.util.patchQueryResult(
            'getProfilePost',
            post_id,
            context.undoPost,
          ),
        );
        dispatch(
          profileApi.util.patchQueryResult(
            'getProfilePosts',
            username,
            context.undoPosts,
          ),
        );
        dispatch(
          homePostApi.util.patchQueryResult(
            'getPost',
            post_id,
            context.undoPost,
          ),
        );
        dispatch(
          homePostApi.util.patchQueryResult(
            'getPosts',
            undefined,
            context.undoPosts,
          ),
        );
      },
    }),
    removeLikeProfilePost: build.mutation<
      { success: boolean },
      { username: string; post_id: string }
    >({
      query({ post_id }) {
        return {
          url: `likes?post_id=${post_id}`,
          method: 'DELETE',
        };
      },

      onStart({ username, post_id }, { dispatch, context }) {
        context.undoPost = dispatch(
          profileApi.util.updateQueryResult(
            'getProfilePost',
            post_id,
            draft => {
              draft.count_likes -= 1;
              draft.is_like = false;
            },
          ),
        ).inversePatches;

        context.undoPosts = dispatch(
          profileApi.util.updateQueryResult(
            'getProfilePosts',
            username,
            draft => {
              const postIndex = draft.data.findIndex(
                post => post.id === String(post_id),
              );
              if (postIndex !== -1) {
                draft.data[postIndex].is_like = false;
                draft.data[postIndex].count_likes -= 1;
              }
            },
          ),
        ).inversePatches;
        context.undoPost = dispatch(
          homePostApi.util.updateQueryResult('getPost', post_id, draft => {
            draft.count_likes -= 1;
            draft.is_like = false;
          }),
        ).inversePatches;

        context.undoPosts = dispatch(
          homePostApi.util.updateQueryResult('getPosts', undefined, draft => {
            const postIndex = draft.data.findIndex(
              post => post.id === String(post_id),
            );
            if (postIndex !== -1) {
              draft.data[postIndex].is_like = false;
              draft.data[postIndex].count_likes -= 1;
            }
          }),
        ).inversePatches;
      },
      onError({ username, post_id }, { dispatch, context }) {
        console.log('rolling back like', username, context);
        dispatch(
          profileApi.util.patchQueryResult(
            'getProfilePost',
            post_id,
            context.undoPost,
          ),
        );
        dispatch(
          profileApi.util.patchQueryResult(
            'getProfilePosts',
            username,
            context.undoPosts,
          ),
        );
        dispatch(
          homePostApi.util.patchQueryResult(
            'getPost',
            post_id,
            context.undoPost,
          ),
        );
        dispatch(
          homePostApi.util.patchQueryResult(
            'getPosts',
            undefined,
            context.undoPosts,
          ),
        );
      },
    }),
  }),
});

export const {
  useGetProfilePostsQuery,
  useGetProfilePostQuery,
  useRemoveLikeProfilePostMutation,
  useCreateLikeProfilePostMutation,
} = profileApi;
phryneas commented 3 years ago

Well, for the code itself: you are using context.undoPost twice, overriding the first value in the third call. Same for the 2nd and 4th call. This will cause a bug later on - use different properties there.

That aside: this is an issue tracker, not a coding tips forum. Please use more appropriate channels like stackoverflow or the redux channel on the reactiflux discord server.

Considering this particular problem: it is good that you investigate this early, but I think you are completely overengineering this. Yes, it will cost a few api calls. But realistically, your app does not have a million users. And if it had, not all of them would be clicking the same second (unless you have tinder-like interaction rates). A single baremetal server can serve tens or even hundreds of thousands concurrent users without problems if your server implementation is half-decent. For under a hundred bucks per month. Compare that to your own hourly wage and the time you need to implement this. Then add a few more servers and you still can run these for years for the money it takes you to work on that for just a few days. 90% of the time it's not worth it and if you notice at any point that it is necessary AND worth it, you can always do it later.