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 69279a8932f..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 @@ -1,4 +1,4 @@ -import { CSSProperties } from 'react'; +import { CSSProperties, useCallback, useMemo } from 'react'; import type { MantineStyleProp } from '../../Box'; import { FactoryPayload } from '../../factory'; import { @@ -7,6 +7,7 @@ import { useMantineTheme, useMantineWithStaticClasses, } 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'; @@ -36,6 +37,18 @@ export type GetStylesApi = ( style: CSSProperties; }; +// Omit objects from props to avoid unnecessary rerenders +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] = value; + } + } + + return result as Partial; +} + export function useStyles({ name, classes, @@ -54,46 +67,72 @@ export function useStyles({ const classNamesPrefix = useMantineClassNamesPrefix(); const withStaticClasses = useMantineWithStaticClasses(); const headless = useMantineIsHeadless(); - 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[], + [JSON.stringify(name)] // "name" can be a new array on every render + ); + const memoizedProps = useMemoObject(omitObjectProps(props)); const { withStylesTransform, getTransformedStyles } = useStylesTransform({ - props, + props: memoizedProps, stylesCtx, themeName, }); - return (selector, options) => ({ - className: getClassName({ + return useCallback( + (selector, options) => ({ + className: getClassName({ + theme, + options, + themeName, + selector, + classNamesPrefix, + classNames, + classes, + unstyled, + className, + rootSelector, + props: memoizedProps, + stylesCtx, + withStaticClasses, + headless, + transformedStyles: getTransformedStyles([options?.styles, styles]), + }), + + style: getStyle({ + theme, + themeName, + selector, + options, + props: memoizedProps, + stylesCtx, + rootSelector, + styles, + style, + vars, + varsResolver, + headless, + withStylesTransform, + }), + }), + [ theme, - options, themeName, - selector, classNamesPrefix, classNames, classes, unstyled, className, rootSelector, - props, - stylesCtx, - withStaticClasses, - headless, - transformedStyles: getTransformedStyles([options?.styles, styles]), - }), - - style: getStyle({ - theme, - themeName, - selector, - options, - props, + memoizedProps, stylesCtx, - rootSelector, styles, 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, 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 54ca5915650..f08cc1206e0 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 { 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; +}