diff --git a/apps/portal/src/pages/questions/browse.tsx b/apps/portal/src/pages/questions/browse.tsx index 34cbd6c1..c3b25e59 100644 --- a/apps/portal/src/pages/questions/browse.tsx +++ b/apps/portal/src/pages/questions/browse.tsx @@ -481,7 +481,11 @@ export default function QuestionsBrowsePage() { Home - {APP_TITLE} -
+
diff --git a/apps/portal/src/utils/questions/useVote.ts b/apps/portal/src/utils/questions/useVote.ts index e35a608a..f1e8b864 100644 --- a/apps/portal/src/utils/questions/useVote.ts +++ b/apps/portal/src/utils/questions/useVote.ts @@ -1,9 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback } from 'react'; -import type { Vote } from '@prisma/client'; +import type { InfiniteData } from 'react-query'; +import { Vote } from '@prisma/client'; import { trpc } from '../trpc'; +import type { Question } from '~/types/questions'; + type UseVoteOptions = { setDownVote: () => void; setNoVote: () => void; @@ -46,12 +49,78 @@ type MutationKey = Parameters[0]; type QueryKey = Parameters[0][0]; export const useQuestionVote = (id: string) => { + const utils = trpc.useContext(); + return useVote(id, { idKey: 'questionId', invalidateKeys: [ - 'questions.questions.getQuestionsByFilter', - 'questions.questions.getQuestionById', + // 'questions.questions.getQuestionById', + // 'questions.questions.getQuestionsByFilterAndContent', ], + onMutate: async (previousVote, currentVote) => { + const questionQueries = utils.queryClient.getQueriesData([ + 'questions.questions.getQuestionsByFilterAndContent', + ]); + + const getVoteValue = (vote: Vote | null) => { + if (vote === Vote.UPVOTE) { + return 1; + } + if (vote === Vote.DOWNVOTE) { + return -1; + } + return 0; + }; + + const voteValueChange = + getVoteValue(currentVote) - getVoteValue(previousVote); + + for (const [key, query] of questionQueries) { + if (query === undefined) { + continue; + } + + const { pages, ...restQuery } = query as InfiniteData<{ + data: Array; + }>; + + const newQuery = { + pages: pages.map(({ data, ...restPage }) => ({ + data: data.map((question) => { + if (question.id === id) { + const { numVotes, ...restQuestion } = question; + return { + numVotes: numVotes + voteValueChange, + ...restQuestion, + }; + } + return question; + }), + ...restPage, + })), + ...restQuery, + }; + + utils.queryClient.setQueryData(key, newQuery); + } + + const prevQuestion = utils.queryClient.getQueryData([ + 'questions.questions.getQuestionById', + { + id, + }, + ]) as Question; + + const newQuestion = { + ...prevQuestion, + numVotes: prevQuestion.numVotes + voteValueChange, + }; + + utils.queryClient.setQueryData( + ['questions.questions.getQuestionById', { id }], + newQuestion, + ); + }, query: 'questions.questions.user.getVote', setDownVoteKey: 'questions.questions.user.setDownVote', setNoVoteKey: 'questions.questions.user.setNoVote', @@ -63,8 +132,8 @@ export const useAnswerVote = (id: string) => { return useVote(id, { idKey: 'answerId', invalidateKeys: [ - 'questions.answers.getAnswers', 'questions.answers.getAnswerById', + 'questions.answers.getAnswers', ], query: 'questions.answers.user.getVote', setDownVoteKey: 'questions.answers.user.setDownVote', @@ -95,9 +164,17 @@ export const useAnswerCommentVote = (id: string) => { }); }; +type InvalidateFunction = ( + previousVote: Vote | null, + currentVote: Vote | null, +) => Promise; + type VoteProps = { idKey: string; - invalidateKeys: Array; + invalidateKeys: Array; + onMutate?: InvalidateFunction; + + // Invalidate: Partial>; query: VoteQueryKey; setDownVoteKey: MutationKey; setNoVoteKey: MutationKey; @@ -116,6 +193,7 @@ export const useVote = ( const { idKey, invalidateKeys, + onMutate, query, setDownVoteKey, setNoVoteKey, @@ -125,11 +203,16 @@ export const useVote = ( const onVoteUpdate = useCallback(() => { // TODO: Optimise query invalidation - utils.invalidateQueries([query, { [idKey]: id } as any]); + // utils.invalidateQueries([query, { [idKey]: id } as any]); for (const invalidateKey of invalidateKeys) { - utils.invalidateQueries([invalidateKey]); + utils.invalidateQueries(invalidateKey); + // If (invalidateFunction === null) { + // utils.invalidateQueries([invalidateKey as QueryKey]); + // } else { + // invalidateFunction(utils, previousVote, currentVote); + // } } - }, [id, idKey, utils, query, invalidateKeys]); + }, [utils, invalidateKeys]); const { data } = trpc.useQuery([ query, @@ -143,7 +226,7 @@ export const useVote = ( const { mutate: setUpVote } = trpc.useMutation( setUpVoteKey, { - onError: (err, variables, context) => { + onError: (_error, _variables, context) => { if (context !== undefined) { utils.setQueryData([query], context.previousData); } @@ -154,6 +237,11 @@ export const useVote = ( [query, { [idKey]: id } as any], ); + const currentData = { + ...(vote as any), + vote: Vote.UPVOTE, + } as BackendVote; + utils.setQueryData( [ query, @@ -161,9 +249,11 @@ export const useVote = ( [idKey]: id, } as any, ], - vote as any, + currentData as any, ); - return { currentData: vote, previousData }; + + await onMutate?.(previousData?.vote ?? null, currentData?.vote ?? null); + return { currentData, previousData }; }, onSettled: onVoteUpdate, }, @@ -171,7 +261,7 @@ export const useVote = ( const { mutate: setDownVote } = trpc.useMutation( setDownVoteKey, { - onError: (error, variables, context) => { + onError: (_error, _variables, context) => { if (context !== undefined) { utils.setQueryData([query], context.previousData); } @@ -182,6 +272,11 @@ export const useVote = ( [query, { [idKey]: id } as any], ); + const currentData = { + ...vote, + vote: Vote.DOWNVOTE, + } as BackendVote; + utils.setQueryData( [ query, @@ -189,9 +284,11 @@ export const useVote = ( [idKey]: id, } as any, ], - vote, + currentData as any, ); - return { currentData: vote, previousData }; + + await onMutate?.(previousData?.vote ?? null, currentData?.vote ?? null); + return { currentData, previousData }; }, onSettled: onVoteUpdate, }, @@ -200,23 +297,31 @@ export const useVote = ( const { mutate: setNoVote } = trpc.useMutation( setNoVoteKey, { - onError: (err, variables, context) => { + onError: (_error, _variables, context) => { if (context !== undefined) { utils.setQueryData([query], context.previousData); } }, - onMutate: async (vote) => { + onMutate: async () => { await utils.queryClient.cancelQueries([query, { [idKey]: id } as any]); - utils.setQueryData( + const previousData = utils.queryClient.getQueryData( + [query, { [idKey]: id } as any], + ); + const currentData: BackendVote | null = null; + + utils.queryClient.setQueryData( [ query, { [idKey]: id, } as any, ], - null as any, + currentData, ); - return { currentData: null, previousData: vote }; + + await onMutate?.(previousData?.vote ?? null, null); + + return { currentData, previousData }; }, onSettled: onVoteUpdate, },