-
Notifications
You must be signed in to change notification settings - Fork 367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: [M3-8981] - User Preferences optimization with selector pattern (Part 1) #11386
Changes from all commits
ad02ddb
a31c62c
08308d9
4fe21d3
605fa4f
b7e1bc9
d185cc4
f4f9070
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,7 +93,12 @@ export const PrimaryNav = (props: PrimaryNavProps) => { | |
|
||
const { isIAMEnabled, isIAMBeta } = useIsIAMEnabled(); | ||
|
||
const { data: preferences } = usePreferences(); | ||
const { data: collapsedSideNavPreference } = usePreferences( | ||
(preferences) => preferences?.collapsedSideNavProductFamilies | ||
); | ||
|
||
const collapsedAccordions = collapsedSideNavPreference ?? []; | ||
|
||
const { mutateAsync: updatePreferences } = useMutatePreferences(); | ||
|
||
const productFamilyLinkGroups: ProductFamilyLinkGroup< | ||
|
@@ -253,10 +258,6 @@ export const PrimaryNav = (props: PrimaryNavProps) => { | |
] | ||
); | ||
|
||
const [collapsedAccordions, setCollapsedAccordions] = React.useState< | ||
number[] | ||
>(preferences?.collapsedSideNavProductFamilies ?? []); | ||
Comment on lines
-256
to
-258
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to use extra state here. Because our user preferences optimistically update, we can essentially treat it as state. |
||
|
||
const accordionClicked = (index: number) => { | ||
let updatedCollapsedAccordions; | ||
if (collapsedAccordions.includes(index)) { | ||
|
@@ -266,13 +267,11 @@ export const PrimaryNav = (props: PrimaryNavProps) => { | |
updatePreferences({ | ||
collapsedSideNavProductFamilies: updatedCollapsedAccordions, | ||
}); | ||
setCollapsedAccordions(updatedCollapsedAccordions); | ||
} else { | ||
updatedCollapsedAccordions = [...collapsedAccordions, index]; | ||
updatePreferences({ | ||
collapsedSideNavProductFamilies: updatedCollapsedAccordions, | ||
}); | ||
setCollapsedAccordions(updatedCollapsedAccordions); | ||
} | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,6 @@ import type { ExtendedStatus } from './utils'; | |
import type { Config } from '@linode/api-v4/lib/linodes/types'; | ||
import type { APIError } from '@linode/api-v4/lib/types'; | ||
import type { RouteComponentProps } from 'react-router-dom'; | ||
import type { PreferenceToggleProps } from 'src/components/PreferenceToggle/PreferenceToggle'; | ||
import type { WithFeatureFlagProps } from 'src/containers/flags.container'; | ||
import type { WithProfileProps } from 'src/containers/profile.container'; | ||
import type { DialogType } from 'src/features/Linodes/types'; | ||
|
@@ -308,17 +307,17 @@ class ListLinodes extends React.Component<CombinedProps, State> { | |
)} | ||
<DocumentTitleSegment segment="Linodes" /> | ||
<ProductInformationBanner bannerLocation="Linodes" /> | ||
<PreferenceToggle<boolean> | ||
<PreferenceToggle | ||
preferenceKey="linodes_group_by_tag" | ||
preferenceOptions={[false, true]} | ||
toggleCallbackFn={sendGroupByAnalytic} | ||
> | ||
{({ | ||
preference: linodesAreGrouped, | ||
togglePreference: toggleGroupLinodes, | ||
}: PreferenceToggleProps<boolean>) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I improved the type-safety of |
||
}) => { | ||
return ( | ||
<PreferenceToggle<'grid' | 'list'> | ||
<PreferenceToggle | ||
preferenceKey="linodes_view_style" | ||
preferenceOptions={['list', 'grid']} | ||
toggleCallbackFn={this.changeView} | ||
|
@@ -331,7 +330,7 @@ class ListLinodes extends React.Component<CombinedProps, State> { | |
{({ | ||
preference: linodeViewPreference, | ||
togglePreference: toggleLinodeView, | ||
}: PreferenceToggleProps<'grid' | 'list'>) => { | ||
}) => { | ||
return ( | ||
<React.Fragment> | ||
<React.Fragment> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,42 @@ | ||
import { updateUserPreferences } from '@linode/api-v4'; | ||
import { | ||
QueryClient, | ||
useMutation, | ||
useQuery, | ||
useQueryClient, | ||
} from '@tanstack/react-query'; | ||
|
||
import { ManagerPreferences } from 'src/types/ManagerPreferences'; | ||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||
|
||
import { queryPresets } from '../base'; | ||
import { profileQueries } from './profile'; | ||
|
||
import type { APIError } from '@linode/api-v4'; | ||
import type { QueryClient } from '@tanstack/react-query'; | ||
import type { ManagerPreferences } from 'src/types/ManagerPreferences'; | ||
|
||
export const usePreferences = (enabled = true) => | ||
useQuery<ManagerPreferences, APIError[]>({ | ||
// Reference for this pattern: https://tkdodo.eu/blog/react-query-data-transformations#3-using-the-select-option | ||
export const usePreferences = <TData = ManagerPreferences>( | ||
bnussman-akamai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
select?: (data: ManagerPreferences | undefined) => TData, | ||
enabled = true | ||
) => | ||
useQuery({ | ||
...profileQueries.preferences, | ||
...queryPresets.oneTimeFetch, | ||
enabled, | ||
select, | ||
}); | ||
|
||
export const useMutatePreferences = (replace = false) => { | ||
const { data: preferences } = usePreferences(!replace); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this use of Using |
||
const queryClient = useQueryClient(); | ||
|
||
return useMutation< | ||
ManagerPreferences, | ||
APIError[], | ||
Partial<ManagerPreferences> | ||
>({ | ||
mutationFn: (data) => | ||
updateUserPreferences({ | ||
...(!replace && preferences !== undefined ? preferences : {}), | ||
...data, | ||
}), | ||
async mutationFn(data) { | ||
if (replace) { | ||
return updateUserPreferences(data); | ||
} | ||
const existingPreferences = await queryClient.ensureQueryData<ManagerPreferences>( | ||
profileQueries.preferences | ||
); | ||
return updateUserPreferences({ ...existingPreferences, ...data }); | ||
}, | ||
onMutate: (data) => updatePreferenceData(data, replace, queryClient), | ||
}); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { UserPreferences } from '@linode/api-v4'; | ||
import type { AclpConfig } from '@linode/api-v4'; | ||
import type { Order } from 'src/hooks/useOrder'; | ||
import type { ThemeChoice } from 'src/utilities/theme'; | ||
|
||
|
@@ -18,9 +18,11 @@ export interface DismissedNotification { | |
label?: string; | ||
} | ||
|
||
export interface ManagerPreferences extends UserPreferences { | ||
export interface ManagerPreferences { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
aclpPreference?: AclpConfig; // Why is this type in @linode/api-v4? | ||
avatarColor?: string; | ||
backups_cta_dismissed?: boolean; | ||
collapsedSideNavProductFamilies?: number[]; | ||
desktop_sidebar_open?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These were types missing... Now that type-safety is better, developers will see type errors that will catch this |
||
dismissed_notifications?: Record<string, DismissedNotification>; | ||
domains_group_by_tag?: boolean; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new pattern in-use! β¨
Before: The primary nav would re-render when any preference was updated
After: The primary nav will only re-render when
preferences.collapsedSideNavProductFamilies
updates πThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be unrelated, but Iβm noticing that PrimaryNav re-renders when navigating to Create
Image
, while it doesnβt re-render when selecting any products from the Side Nav or any other Create {Product}.understood - it's a Create Image, but is PrimaryNav supposed to get re-rendered in such cases??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that is unrelated. From my understanding, the primary nav will re-render when we change routes no matter what.