Skip to content

Commit

Permalink
feat: 꿀조합 좋아요에 낙관적 업데이트 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
xodms0309 committed Nov 1, 2023
1 parent c92e575 commit b502092
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import styled from 'styled-components';

import { SvgIcon } from '@/components/Common';
import { useTimeout } from '@/hooks/common';
import { useToastActionContext } from '@/hooks/context';
import { useRecipeFavoriteMutation } from '@/hooks/queries/recipe';

interface RecipeFavoriteProps {
Expand All @@ -13,23 +12,28 @@ interface RecipeFavoriteProps {
recipeId: number;
}

const RecipeFavorite = ({ recipeId, favorite, favoriteCount }: RecipeFavoriteProps) => {
const [isFavorite, setIsFavorite] = useState(favorite);
const [currentFavoriteCount, setCurrentFavoriteCount] = useState(favoriteCount);
const { toast } = useToastActionContext();
const RecipeFavoriteButton = ({ recipeId, favorite, favoriteCount }: RecipeFavoriteProps) => {
const initialFavoriteState = {
isFavorite: favorite,
currentFavoriteCount: favoriteCount,
};

const [favoriteInfo, setFavoriteInfo] = useState(initialFavoriteState);

const { mutate } = useRecipeFavoriteMutation(Number(recipeId));
const { isFavorite, currentFavoriteCount } = favoriteInfo;

const handleToggleFavorite = async () => {
setFavoriteInfo((prev) => ({
isFavorite: !prev.isFavorite,
currentFavoriteCount: isFavorite ? prev.currentFavoriteCount - 1 : prev.currentFavoriteCount + 1,
}));

mutate(
{ favorite: !isFavorite },
{
onSuccess: () => {
setIsFavorite((prev) => !prev);
setCurrentFavoriteCount((prev) => (isFavorite ? prev - 1 : prev + 1));
},
onError: () => {
toast.error('꿀조합 좋아요를 다시 시도해주세요.');
setFavoriteInfo(initialFavoriteState);
},
}
);
Expand All @@ -51,7 +55,7 @@ const RecipeFavorite = ({ recipeId, favorite, favoriteCount }: RecipeFavoritePro
);
};

export default RecipeFavorite;
export default RecipeFavoriteButton;

const FavoriteButton = styled(Button)`
display: flex;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Recipe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export { default as RecipeUsedProducts } from './RecipeUsedProducts/RecipeUsedPr
export { default as RecipeItem } from './RecipeItem/RecipeItem';
export { default as RecipeList } from './RecipeList/RecipeList';
export { default as RecipeRegisterForm } from './RecipeRegisterForm/RecipeRegisterForm';
export { default as RecipeFavorite } from './RecipeFavorite/RecipeFavorite';
export { default as RecipeFavoriteButton } from './RecipeFavoriteButton/RecipeFavoriteButton';
export { default as CommentItem } from './CommentItem/CommentItem';
export { default as CommentForm } from './CommentForm/CommentForm';
export { default as CommentList } from './CommentList/CommentList';
23 changes: 22 additions & 1 deletion frontend/src/hooks/queries/recipe/useRecipeFavoriteMutation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { recipeApi } from '@/apis';
import { useToastActionContext } from '@/hooks/context';
import type { RecipeFavoriteRequestBody } from '@/types/recipe';

const headers = { 'Content-Type': 'application/json' };
Expand All @@ -11,10 +12,30 @@ const patchRecipeFavorite = (recipeId: number, body: RecipeFavoriteRequestBody)

const useRecipeFavoriteMutation = (recipeId: number) => {
const queryClient = useQueryClient();
const { toast } = useToastActionContext();

const queryKey = ['recipeDetail', recipeId];

return useMutation({
mutationFn: (body: RecipeFavoriteRequestBody) => patchRecipeFavorite(recipeId, body),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['recipeDetail', recipeId] }),
onMutate: async (newFavoriteRequest) => {
await queryClient.cancelQueries({ queryKey: queryKey });

const previousRequest = queryClient.getQueryData(queryKey);
queryClient.setQueryData(queryKey, newFavoriteRequest);

return { previousRequest };
},
onError: (error, _, context) => {
queryClient.setQueryData(queryKey, context?.previousRequest);
if (error instanceof Error) {
toast.error(error.message);
return;
}

toast.error('꿀조합 좋아요를 다시 시도해주세요.');
},
onSettled: () => queryClient.invalidateQueries({ queryKey: queryKey }),
});
};

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/RecipeDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import styled from 'styled-components';

import RecipePreviewImage from '@/assets/plate.svg';
import { ErrorBoundary, ErrorComponent, Loading, SectionTitle } from '@/components/Common';
import { CommentForm, CommentList, RecipeFavorite } from '@/components/Recipe';
import { CommentForm, CommentList, RecipeFavoriteButton } from '@/components/Recipe';
import { useRecipeDetailQuery } from '@/hooks/queries/recipe';
import { getFormattedDate } from '@/utils/date';

Expand Down Expand Up @@ -46,7 +46,7 @@ export const RecipeDetailPage = () => {
<Text color={theme.textColors.info}> {getFormattedDate(createdAt)}</Text>
</div>
</AuthorWrapper>
<RecipeFavorite recipeId={id} favorite={favorite} favoriteCount={favoriteCount} />
<RecipeFavoriteButton recipeId={id} favorite={favorite} favoriteCount={favoriteCount} />
</AuthorFavoriteWrapper>
<Spacing size={24} />
<RecipeUsedProductsWrapper>
Expand Down

0 comments on commit b502092

Please sign in to comment.