nicolium: migrate status optimistic responses
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -1,57 +1,11 @@
|
||||
const REBLOG_REQUEST = 'REBLOG_REQUEST' as const;
|
||||
const REBLOG_FAIL = 'REBLOG_FAIL' as const;
|
||||
|
||||
const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST' as const;
|
||||
const FAVOURITE_FAIL = 'FAVOURITE_FAIL' as const;
|
||||
|
||||
const DISLIKE_REQUEST = 'DISLIKE_REQUEST' as const;
|
||||
const DISLIKE_FAIL = 'DISLIKE_FAIL' as const;
|
||||
|
||||
const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST' as const;
|
||||
const UNREBLOG_FAIL = 'UNREBLOG_FAIL' as const;
|
||||
|
||||
const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST' as const;
|
||||
|
||||
const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST' as const;
|
||||
|
||||
const PIN_SUCCESS = 'PIN_SUCCESS' as const;
|
||||
|
||||
const UNPIN_SUCCESS = 'UNPIN_SUCCESS' as const;
|
||||
|
||||
type InteractionsAction =
|
||||
| {
|
||||
type:
|
||||
| typeof REBLOG_REQUEST
|
||||
| typeof UNREBLOG_REQUEST
|
||||
| typeof FAVOURITE_REQUEST
|
||||
| typeof UNFAVOURITE_REQUEST
|
||||
| typeof DISLIKE_REQUEST
|
||||
| typeof UNDISLIKE_REQUEST;
|
||||
statusId: string;
|
||||
}
|
||||
| {
|
||||
type: typeof REBLOG_FAIL | typeof UNREBLOG_FAIL | typeof FAVOURITE_FAIL | typeof DISLIKE_FAIL;
|
||||
statusId: string;
|
||||
error: unknown;
|
||||
}
|
||||
| {
|
||||
type: typeof PIN_SUCCESS | typeof UNPIN_SUCCESS;
|
||||
statusId: string;
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
export {
|
||||
REBLOG_REQUEST,
|
||||
REBLOG_FAIL,
|
||||
FAVOURITE_REQUEST,
|
||||
FAVOURITE_FAIL,
|
||||
DISLIKE_REQUEST,
|
||||
DISLIKE_FAIL,
|
||||
UNREBLOG_REQUEST,
|
||||
UNREBLOG_FAIL,
|
||||
UNFAVOURITE_REQUEST,
|
||||
UNDISLIKE_REQUEST,
|
||||
PIN_SUCCESS,
|
||||
UNPIN_SUCCESS,
|
||||
type InteractionsAction,
|
||||
type InteractionsAction = {
|
||||
type: typeof PIN_SUCCESS | typeof UNPIN_SUCCESS;
|
||||
statusId: string;
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
export { PIN_SUCCESS, UNPIN_SUCCESS, type InteractionsAction };
|
||||
|
||||
@ -15,6 +15,7 @@ import toast, { type IToastOptions } from '@/toast';
|
||||
import { queryKeys } from '../keys';
|
||||
import { filterById } from '../utils/filter-id';
|
||||
|
||||
import type { NormalizedStatus } from '@/reducers/statuses';
|
||||
import type { EmojiReaction, PaginatedResponse } from 'pl-api';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -47,6 +48,33 @@ const minifyEmojiReaction = ({ accounts, ...reaction }: EmojiReaction) => reacti
|
||||
|
||||
type MinifiedEmojiReaction = ReturnType<typeof minifyEmojiReaction>;
|
||||
|
||||
const updateStatus = (
|
||||
statusId: string,
|
||||
changes: Partial<NormalizedStatus> | ((status: NormalizedStatus) => NormalizedStatus),
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
) => {
|
||||
const previousStatus = queryClient.getQueryData<NormalizedStatus>(
|
||||
queryKeys.statuses.show(statusId),
|
||||
);
|
||||
if (!previousStatus) return;
|
||||
|
||||
const newStatus =
|
||||
typeof changes === 'function' ? changes(previousStatus) : { ...previousStatus, ...changes };
|
||||
queryClient.setQueryData(queryKeys.statuses.show(statusId), newStatus);
|
||||
|
||||
return { previousStatus };
|
||||
};
|
||||
|
||||
const restorePreviousStatus = (
|
||||
statusId: string,
|
||||
context: { previousStatus?: NormalizedStatus } | undefined,
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
) => {
|
||||
if (context?.previousStatus) {
|
||||
queryClient.setQueryData(queryKeys.statuses.show(statusId), context.previousStatus);
|
||||
}
|
||||
};
|
||||
|
||||
const useStatusReactions = (statusId: string, emoji?: string) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
@ -75,8 +103,17 @@ const useFavouriteStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'favourite', statusId],
|
||||
mutationFn: () => client.statuses.favouriteStatus(statusId),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'FAVOURITE_REQUEST', statusId }),
|
||||
onError: () => dispatch<InteractionsAction>({ type: 'UNFAVOURITE_REQUEST', statusId }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
favourited: true,
|
||||
favourites_count: status.favourites_count + 1,
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({
|
||||
@ -94,8 +131,17 @@ const useUnfavouriteStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'favourite', statusId],
|
||||
mutationFn: () => client.statuses.unfavouriteStatus(statusId),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'UNFAVOURITE_REQUEST', statusId }),
|
||||
onError: () => dispatch<InteractionsAction>({ type: 'FAVOURITE_REQUEST', statusId }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
favourited: false,
|
||||
favourites_count: Math.max(0, status.favourites_count - 1),
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({
|
||||
@ -113,8 +159,17 @@ const useDislikeStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'dislike', statusId],
|
||||
mutationFn: () => client.statuses.dislikeStatus(statusId),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'DISLIKE_REQUEST', statusId }),
|
||||
onError: () => dispatch<InteractionsAction>({ type: 'UNDISLIKE_REQUEST', statusId }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
disliked: true,
|
||||
dislikes_count: status.dislikes_count + 1,
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.accountsLists.statusDislikes(statusId) });
|
||||
@ -130,8 +185,17 @@ const useUndislikeStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'dislike', statusId],
|
||||
mutationFn: () => client.statuses.undislikeStatus(statusId),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'UNDISLIKE_REQUEST', statusId }),
|
||||
onError: () => dispatch<InteractionsAction>({ type: 'DISLIKE_REQUEST', statusId }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
disliked: false,
|
||||
dislikes_count: Math.max(0, status.dislikes_count - 1),
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.accountsLists.statusDislikes(statusId) });
|
||||
@ -147,8 +211,17 @@ const useReblogStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'reblog', statusId],
|
||||
mutationFn: (visibility?: string) => client.statuses.reblogStatus(statusId, visibility),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'REBLOG_REQUEST', statusId }),
|
||||
onError: (error) => dispatch<InteractionsAction>({ type: 'REBLOG_FAIL', statusId, error }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
reblogged: true,
|
||||
reblogs_count: status.reblogs_count + 1,
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.accountsLists.statusReblogs(statusId) });
|
||||
@ -164,8 +237,17 @@ const useUnreblogStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'reblog', statusId],
|
||||
mutationFn: () => client.statuses.unreblogStatus(statusId),
|
||||
onMutate: () => dispatch<InteractionsAction>({ type: 'UNREBLOG_REQUEST', statusId }),
|
||||
onError: (error) => dispatch<InteractionsAction>({ type: 'UNREBLOG_FAIL', statusId, error }),
|
||||
onMutate: () =>
|
||||
updateStatus(
|
||||
statusId,
|
||||
(status) => ({
|
||||
...status,
|
||||
reblogged: false,
|
||||
reblogs_count: Math.max(0, status.reblogs_count - 1),
|
||||
}),
|
||||
queryClient,
|
||||
),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.accountsLists.statusReblogs(statusId) });
|
||||
@ -193,6 +275,8 @@ const useBookmarkStatus = (statusId: string) => {
|
||||
});
|
||||
return client.statuses.bookmarkStatus(statusId, folderId);
|
||||
},
|
||||
onMutate: () => updateStatus(statusId, { bookmarked: true }, queryClient),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status, _, folderId) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.accountsLists.statusReblogs(statusId) });
|
||||
@ -247,6 +331,8 @@ const useUnbookmarkStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'bookmark', statusId],
|
||||
mutationFn: () => client.statuses.unbookmarkStatus(statusId),
|
||||
onMutate: () => updateStatus(statusId, { bookmarked: false }, queryClient),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSettled: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
|
||||
@ -272,6 +358,8 @@ const usePinStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'pin', statusId],
|
||||
mutationFn: () => client.statuses.pinStatus(statusId),
|
||||
onMutate: () => updateStatus(statusId, { pinned: true }, queryClient),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSuccess: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.statusLists.pins(account!.id) });
|
||||
@ -293,6 +381,8 @@ const useUnpinStatus = (statusId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'unpin', statusId],
|
||||
mutationFn: () => client.statuses.unpinStatus(statusId),
|
||||
onMutate: () => updateStatus(statusId, { pinned: false }, queryClient),
|
||||
onError: (_, __, context) => restorePreviousStatus(statusId, context, queryClient),
|
||||
onSuccess: (status) => {
|
||||
dispatch(importEntities({ statuses: [status] }));
|
||||
queryClient.setQueryData(queryKeys.statusLists.pins(account!.id), filterById(statusId));
|
||||
|
||||
@ -23,19 +23,6 @@ import {
|
||||
type EventsAction,
|
||||
} from '@/actions/events';
|
||||
import { STATUS_IMPORT, STATUSES_IMPORT, type ImporterAction } from '@/actions/importer';
|
||||
import {
|
||||
REBLOG_REQUEST,
|
||||
REBLOG_FAIL,
|
||||
UNREBLOG_REQUEST,
|
||||
UNREBLOG_FAIL,
|
||||
FAVOURITE_REQUEST,
|
||||
UNFAVOURITE_REQUEST,
|
||||
FAVOURITE_FAIL,
|
||||
DISLIKE_REQUEST,
|
||||
UNDISLIKE_REQUEST,
|
||||
DISLIKE_FAIL,
|
||||
type InteractionsAction,
|
||||
} from '@/actions/interactions';
|
||||
import {
|
||||
STATUS_CREATE_REQUEST,
|
||||
STATUS_CREATE_FAIL,
|
||||
@ -259,49 +246,11 @@ const decrementReplyCount = (
|
||||
return state;
|
||||
};
|
||||
|
||||
/** Simulate favourite/unfavourite of status for optimistic interactions */
|
||||
const simulateFavourite = (state: State, statusId: string, favourited: boolean) => {
|
||||
const status = state[statusId];
|
||||
if (!status) return state;
|
||||
|
||||
const delta = favourited ? +1 : -1;
|
||||
|
||||
const updatedStatus = {
|
||||
...status,
|
||||
favourited,
|
||||
favourites_count: Math.max(0, status.favourites_count + delta),
|
||||
};
|
||||
|
||||
state[statusId] = updatedStatus;
|
||||
};
|
||||
|
||||
/** Simulate dislike/undislike of status for optimistic interactions */
|
||||
const simulateDislike = (state: State, statusId: string, disliked: boolean) => {
|
||||
const status = state[statusId];
|
||||
if (!status) return state;
|
||||
|
||||
const delta = disliked ? +1 : -1;
|
||||
|
||||
const updatedStatus = {
|
||||
...status,
|
||||
disliked,
|
||||
dislikes_count: Math.max(0, status.dislikes_count + delta),
|
||||
};
|
||||
|
||||
state[statusId] = updatedStatus;
|
||||
};
|
||||
|
||||
const initialState: State = {};
|
||||
|
||||
const statuses = (
|
||||
state = initialState,
|
||||
action:
|
||||
| EmojiReactsAction
|
||||
| EventsAction
|
||||
| ImporterAction
|
||||
| InteractionsAction
|
||||
| StatusesAction
|
||||
| TimelineAction,
|
||||
action: EmojiReactsAction | EventsAction | ImporterAction | StatusesAction | TimelineAction,
|
||||
): State => {
|
||||
switch (action.type) {
|
||||
case STATUS_IMPORT:
|
||||
@ -320,14 +269,6 @@ const statuses = (
|
||||
return action.editing
|
||||
? state
|
||||
: create(state, (draft) => decrementReplyCount(draft, action.params));
|
||||
case FAVOURITE_REQUEST:
|
||||
return create(state, (draft) => simulateFavourite(draft, action.statusId, true));
|
||||
case UNFAVOURITE_REQUEST:
|
||||
return create(state, (draft) => simulateFavourite(draft, action.statusId, false));
|
||||
case DISLIKE_REQUEST:
|
||||
return create(state, (draft) => simulateDislike(draft, action.statusId, true));
|
||||
case UNDISLIKE_REQUEST:
|
||||
return create(state, (draft) => simulateDislike(draft, action.statusId, false));
|
||||
case EMOJI_REACT_REQUEST:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
@ -347,50 +288,6 @@ const statuses = (
|
||||
status.emoji_reactions = simulateUnEmojiReact(status.emoji_reactions, action.emoji);
|
||||
}
|
||||
});
|
||||
case FAVOURITE_FAIL:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.favourited = false;
|
||||
}
|
||||
});
|
||||
case DISLIKE_FAIL:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.disliked = false;
|
||||
}
|
||||
});
|
||||
case REBLOG_REQUEST:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogs_count += 1;
|
||||
status.reblogged = true;
|
||||
}
|
||||
});
|
||||
case REBLOG_FAIL:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogged = false;
|
||||
}
|
||||
});
|
||||
case UNREBLOG_REQUEST:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogs_count = Math.max(0, status.reblogs_count - 1);
|
||||
status.reblogged = false;
|
||||
}
|
||||
});
|
||||
case UNREBLOG_FAIL:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogged = true;
|
||||
}
|
||||
});
|
||||
case STATUS_MUTE_SUCCESS:
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
|
||||
Reference in New Issue
Block a user