From 4dbb7cb10fd94a68a825ece9476ee136a44086bc Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Mon, 22 Jun 2026 18:47:31 +0300 Subject: [PATCH] fix(notifications): init local state after data fetch --- .../notifications-page/NotificationsPage.tsx | 108 ++--------------- .../NotificationsPageContent.tsx | 110 ++++++++++++++++++ .../NotificationsPageFallback.tsx | 29 +++++ 3 files changed, 149 insertions(+), 98 deletions(-) create mode 100644 src/pages/profile/ui/notifications-page/NotificationsPageContent.tsx create mode 100644 src/pages/profile/ui/notifications-page/NotificationsPageFallback.tsx diff --git a/src/pages/profile/ui/notifications-page/NotificationsPage.tsx b/src/pages/profile/ui/notifications-page/NotificationsPage.tsx index 99125bf..a911bbf 100644 --- a/src/pages/profile/ui/notifications-page/NotificationsPage.tsx +++ b/src/pages/profile/ui/notifications-page/NotificationsPage.tsx @@ -1,106 +1,18 @@ 'use client'; -import { useReducer } from 'react'; -import { CardSection, FloatingSaveBar, OptionGroup, Switch } from 'shared/ui'; -import { UserQueries } from 'entities/user'; -import { useUpdateNotifications } from '../../api/useUpdateNotifications'; -import { useQuery } from '@tanstack/react-query'; -import { notificationItems } from '../../config/notifications'; -import { NotificationChannel, Notifications } from '../../model/notifications'; - -type NotificationsState = Notifications | null; - -type NotificationsAction = { - type: 'set'; - payload: NotificationsState; -}; - -const notificationsReducer = ( - _state: NotificationsState, - action: NotificationsAction -): NotificationsState => { - switch (action.type) { - case 'set': - return action.payload; - default: - return _state; +import dynamic from 'next/dynamic'; +import { NotificationsPageFallback } from './NotificationsPageFallback'; + +const NotificationsPageContent = dynamic( + () => import('./NotificationsPageContent').then((mod) => mod.NotificationsPageContent), + { + ssr: false, + loading: () => , } -}; +); function NotificationsPage() { - const query = useQuery(UserQueries.getMe()); - const notifications = query.data?.notifications; - const [localNotifications, dispatchLocalNotifications] = useReducer( - notificationsReducer, - notifications ?? null - ); - const dirty = JSON.stringify(notifications) !== JSON.stringify(localNotifications); - const sendSettings = useUpdateNotifications(); - - const handleToggle = ( - channel: TChannel, - key: keyof Notifications[TChannel], - checked: boolean - ) => { - if (!localNotifications) { - return; - } - - const nextNotifications: Notifications = { - ...localNotifications, - [channel]: { - ...localNotifications[channel], - [key]: checked, - }, - } as Notifications; - - dispatchLocalNotifications({ type: 'set', payload: nextNotifications }); - }; - - return ( - <> - - {Object.entries(notificationItems).map(([channel, items]) => { - const ch = channel as NotificationChannel; - return ( - { - const key = item.key as keyof Notifications[typeof ch]; - - return { - key: item.key, - label: item.label, - input: (props) => ( - handleToggle(ch, key, checked)} - aria-label={item.ariaLabel} - {...props} - /> - ), - }; - })} - /> - ); - })} - - localNotifications && sendSettings.mutate(localNotifications)} - onDiscard={() => - notifications && dispatchLocalNotifications({ type: 'set', payload: notifications }) - } - pending={sendSettings.isPending} - /> - - ); + return ; } export { NotificationsPage }; diff --git a/src/pages/profile/ui/notifications-page/NotificationsPageContent.tsx b/src/pages/profile/ui/notifications-page/NotificationsPageContent.tsx new file mode 100644 index 0000000..05f18da --- /dev/null +++ b/src/pages/profile/ui/notifications-page/NotificationsPageContent.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { useReducer } from 'react'; +import { CardSection, FloatingSaveBar, OptionGroup, Switch } from 'shared/ui'; +import { UserQueries } from 'entities/user'; +import { useUpdateNotifications } from '../../api/useUpdateNotifications'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import { notificationItems } from '../../config/notifications'; +import { NotificationChannel, type Notifications } from '../../model/notifications'; + +type NotificationsState = Notifications | null; + +type NotificationsAction = { + type: 'set'; + payload: NotificationsState; +}; + +const notificationsReducer = ( + _state: NotificationsState, + action: NotificationsAction +): NotificationsState => { + switch (action.type) { + case 'set': + return action.payload; + default: + return _state; + } +}; + +function NotificationsPageContent() { + const query = useSuspenseQuery(UserQueries.getMe()); + const notifications = query.data?.notifications; + const [localNotifications, dispatchLocalNotifications] = useReducer( + notificationsReducer, + notifications ?? null + ); + + const dirty = + !query.isError && + !query.isLoading && + JSON.stringify(notifications) !== JSON.stringify(localNotifications); + const sendSettings = useUpdateNotifications(); + + const handleToggle = ( + channel: TChannel, + key: keyof Notifications[TChannel], + checked: boolean + ) => { + if (!localNotifications) { + return; + } + + const nexNotifications: Notifications = { + ...localNotifications, + [channel]: { + ...localNotifications[channel], + [key]: checked, + }, + } as Notifications; + + dispatchLocalNotifications({ type: 'set', payload: nexNotifications }); + }; + + return ( + <> + + {Object.entries(notificationItems).map(([channel, items]) => { + const ch = channel as NotificationChannel; + return ( + { + const key = item.key as keyof Notifications[typeof ch]; + + return { + key: item.key, + label: item.label, + input: (props) => ( + handleToggle(ch, key, checked)} + aria-label={item.ariaLabel} + {...props} + /> + ), + }; + })} + /> + ); + })} + + localNotifications && sendSettings.mutate(localNotifications)} + onDiscard={() => + notifications && dispatchLocalNotifications({ type: 'set', payload: notifications }) + } + pending={sendSettings.isPending} + /> + + ); +} + +export { NotificationsPageContent }; diff --git a/src/pages/profile/ui/notifications-page/NotificationsPageFallback.tsx b/src/pages/profile/ui/notifications-page/NotificationsPageFallback.tsx new file mode 100644 index 0000000..339c750 --- /dev/null +++ b/src/pages/profile/ui/notifications-page/NotificationsPageFallback.tsx @@ -0,0 +1,29 @@ +import { CardSection, OptionGroup, Skeleton } from 'shared/ui'; + +export function NotificationsPageFallback() { + return ( + + + + ); +} +function OptionGroupSkeleton({ items = 3 }: { items?: number }) { + return ( +
+ + +
+ {Array.from({ length: items }).map((_, i) => ( +
+ + +
+ ))} +
+
+ ); +}