From c6a3124473dc498d01376f43f012c2a8d9a1f868 Mon Sep 17 00:00:00 2001 From: dmitry Date: Tue, 30 Jan 2024 21:06:08 +0100 Subject: [PATCH 1/4] [@mantine/core] Rendering optimisations. Added memoization to crate-safe-context and useStyles. --- .../core/styles-api/use-styles/use-styles.ts | 73 ++++++++++++++----- .../create-safe-context.tsx | 8 +- .../utils/use-memo-object/use-memo-object.ts | 15 ++++ 3 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 packages/@mantine/core/src/core/utils/use-memo-object/use-memo-object.ts diff --git a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts index cbffa59e376..e8b305a66eb 100644 --- a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts +++ b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts @@ -1,7 +1,8 @@ -import { CSSProperties } from 'react'; +import { CSSProperties, useCallback, useMemo } from 'react'; import type { MantineStyleProp } from '../../Box'; import { FactoryPayload } from '../../factory'; import { useMantineClassNamesPrefix, useMantineTheme } from '../../MantineProvider'; +import { useMemoObject } from '../../utils/use-memo-object/use-memo-object'; import { PartialVarsResolver, VarsResolver } from '../create-vars-resolver/create-vars-resolver'; import { ClassNames, ClassNamesArray, GetStylesApiOptions, Styles } from '../styles-api.types'; import { getClassName } from './get-class-name/get-class-name'; @@ -30,6 +31,18 @@ export type GetStylesApi = ( style: CSSProperties; }; +// Omit objects from props to avoid unnecessary rerenders +function omitObjectProps(props: Record): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(props)) { + if (!value || (typeof value !== 'object' && typeof value !== 'function')) { + result[key] = props[key]; + } + } + + return result; +} + export function useStyles({ name, classes, @@ -46,36 +59,58 @@ export function useStyles({ }: UseStylesInput): GetStylesApi { const theme = useMantineTheme(); const classNamesPrefix = useMantineClassNamesPrefix(); - const themeName = (Array.isArray(name) ? name : [name]).filter((n) => n) as string[]; + const themeName = useMemo( + () => (Array.isArray(name) ? name : [name]).filter((n) => n) as string[], + [String(name)] + ); + const memoizedProps = useMemoObject(omitObjectProps(props)); - return (selector, options) => ({ - className: getClassName({ + return useCallback( + (selector, options) => ({ + className: getClassName({ + theme, + options, + themeName, + selector, + classNamesPrefix, + classNames, + classes, + unstyled, + className, + rootSelector, + props: memoizedProps, + stylesCtx, + }), + + style: getStyle({ + theme, + themeName, + selector, + options, + props: memoizedProps, + stylesCtx, + rootSelector, + styles, + style, + vars, + varsResolver, + }), + }), + [ theme, - options, themeName, - selector, classNamesPrefix, classNames, classes, unstyled, className, rootSelector, - props, - stylesCtx, - }), - - style: getStyle({ - theme, - themeName, - selector, - options, - props, + memoizedProps, stylesCtx, - rootSelector, styles, style, vars, varsResolver, - }), - }); + ] + ); } diff --git a/packages/@mantine/core/src/core/utils/create-safe-context/create-safe-context.tsx b/packages/@mantine/core/src/core/utils/create-safe-context/create-safe-context.tsx index 045862c4373..0b17b8ae8a2 100644 --- a/packages/@mantine/core/src/core/utils/create-safe-context/create-safe-context.tsx +++ b/packages/@mantine/core/src/core/utils/create-safe-context/create-safe-context.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext } from 'react'; +import { useMemoObject } from '../use-memo-object/use-memo-object'; export function createSafeContext(errorMessage: string) { const Context = createContext(null); @@ -13,9 +14,10 @@ export function createSafeContext(errorMessage: string) { return ctx; }; - const Provider = ({ children, value }: { value: ContextValue; children: React.ReactNode }) => ( - {children} - ); + const Provider = ({ children, value }: { value: ContextValue; children: React.ReactNode }) => { + const memoizedValue = useMemoObject(value); + return {children}; + }; return [Provider, useSafeContext] as const; } diff --git a/packages/@mantine/core/src/core/utils/use-memo-object/use-memo-object.ts b/packages/@mantine/core/src/core/utils/use-memo-object/use-memo-object.ts new file mode 100644 index 00000000000..411c2bb0493 --- /dev/null +++ b/packages/@mantine/core/src/core/utils/use-memo-object/use-memo-object.ts @@ -0,0 +1,15 @@ +import { useMemo, useRef } from 'react'; + +// Memoize object value to avoid unnecessary renders +export function useMemoObject(value: T): T { + const valueRef = useRef(value); + useMemo(() => { + for (const field in value) { + if (valueRef.current[field] !== value[field]) { + valueRef.current = value; + return; + } + } + }, [value]); + return valueRef.current; +} From 7be0cf490baa7ce6414e5c6a800794a3e89ecdb8 Mon Sep 17 00:00:00 2001 From: dmitry Date: Thu, 20 Jun 2024 15:33:30 +0200 Subject: [PATCH 2/4] [@mantine/core] Rendering optimisations. Added memoization to use-transformed-styles. Fixed useStyles after merge. --- .../core/styles-api/use-styles/use-styles.ts | 6 ++-- .../use-styles/use-transformed-styles.ts | 32 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts index ee18dec285b..e9b50f2770f 100644 --- a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts +++ b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts @@ -71,12 +71,12 @@ export function useStyles({ () => (Array.isArray(name) ? name : [name]).filter((n) => n) as string[], [String(name)] ); + const memoizedProps = useMemoObject(omitObjectProps(props)); const { withStylesTransform, getTransformedStyles } = useStylesTransform({ - props, + props: memoizedProps, stylesCtx, themeName, }); - const memoizedProps = useMemoObject(omitObjectProps(props)); return useCallback( (selector, options) => ({ @@ -129,8 +129,10 @@ export function useStyles({ style, vars, varsResolver, + withStaticClasses, headless, withStylesTransform, + getTransformedStyles, ] ); } diff --git a/packages/@mantine/core/src/core/styles-api/use-styles/use-transformed-styles.ts b/packages/@mantine/core/src/core/styles-api/use-styles/use-transformed-styles.ts index d86b13927f3..495b7f85936 100644 --- a/packages/@mantine/core/src/core/styles-api/use-styles/use-transformed-styles.ts +++ b/packages/@mantine/core/src/core/styles-api/use-styles/use-transformed-styles.ts @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useMantineStylesTransform, useMantineTheme } from '../../MantineProvider'; interface UseTransformedStylesInput { @@ -10,22 +11,25 @@ export function useStylesTransform({ props, stylesCtx, themeName }: UseTransform const theme = useMantineTheme(); const stylesTransform = useMantineStylesTransform()?.(); - const getTransformedStyles = (styles: any[]) => { - if (!stylesTransform) { - return []; - } + const getTransformedStyles = useCallback( + (styles: any[]) => { + if (!stylesTransform) { + return []; + } - const transformedStyles = styles.map((style) => - stylesTransform(style, { props, theme, ctx: stylesCtx }) - ); + const transformedStyles = styles.map((style) => + stylesTransform(style, { props, theme, ctx: stylesCtx }) + ); - return [ - ...transformedStyles, - ...themeName.map((n) => - stylesTransform(theme.components[n]?.styles, { props, theme, ctx: stylesCtx }) - ), - ].filter(Boolean) as Record[]; - }; + return [ + ...transformedStyles, + ...themeName.map((n) => + stylesTransform(theme.components[n]?.styles, { props, theme, ctx: stylesCtx }) + ), + ].filter(Boolean) as Record[]; + }, + [stylesTransform, props, theme, stylesCtx, themeName] + ); return { getTransformedStyles, From e1c29d4dd9c5fe584b43c5144ee0dcdd1c1c5351 Mon Sep 17 00:00:00 2001 From: dmitry Date: Mon, 24 Jun 2024 16:20:24 +0200 Subject: [PATCH 3/4] [@mantine/core] useStyles: Improved internal type of omitObjectProps (#6422) --- .../core/src/core/styles-api/use-styles/use-styles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts index e9b50f2770f..86518b2518d 100644 --- a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts +++ b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts @@ -38,15 +38,15 @@ export type GetStylesApi = ( }; // Omit objects from props to avoid unnecessary rerenders -function omitObjectProps(props: Record): Record { - const result: Record = {}; +function omitObjectProps>(props: TProps): Partial { + const result: Record = {}; // Couldn't be "Partial" because of TS2862 error for (const [key, value] of Object.entries(props)) { if (!value || (typeof value !== 'object' && typeof value !== 'function')) { - result[key] = props[key]; + result[key] = value; } } - return result; + return result as Partial; } export function useStyles({ From fba1033521c23140dc254d32edbcd82ba8df2850 Mon Sep 17 00:00:00 2001 From: dmitry Date: Mon, 24 Jun 2024 16:36:52 +0200 Subject: [PATCH 4/4] [@mantine/core] useStyles: Improved stability of themeName useMemo (#6422) --- .../@mantine/core/src/core/styles-api/use-styles/use-styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts index 86518b2518d..a7aa4b88cf1 100644 --- a/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts +++ b/packages/@mantine/core/src/core/styles-api/use-styles/use-styles.ts @@ -69,7 +69,7 @@ export function useStyles({ const headless = useMantineIsHeadless(); const themeName = useMemo( () => (Array.isArray(name) ? name : [name]).filter((n) => n) as string[], - [String(name)] + [JSON.stringify(name)] // "name" can be a new array on every render ); const memoizedProps = useMemoObject(omitObjectProps(props)); const { withStylesTransform, getTransformedStyles } = useStylesTransform({