From 31ddfeb4cd7bb3fc35ed90ae6c8b6f70dc02ce3f Mon Sep 17 00:00:00 2001 From: Elvira Burchik Date: Fri, 17 Mar 2023 16:52:16 +0100 Subject: [PATCH] Fix variant breakpoint memoization --- src/createRestyleFunction.ts | 28 ++++++- src/test/TestButton.tsx | 45 +++++++++++ src/test/TestContainer.tsx | 68 ++++++++++++++++ src/test/createRestyleFunction.test.ts | 8 +- src/test/useRestyle.test.tsx | 107 +++++++++++++------------ 5 files changed, 201 insertions(+), 55 deletions(-) create mode 100644 src/test/TestButton.tsx create mode 100644 src/test/TestContainer.tsx diff --git a/src/createRestyleFunction.ts b/src/createRestyleFunction.ts index 980a0282..f9aa441b 100644 --- a/src/createRestyleFunction.ts +++ b/src/createRestyleFunction.ts @@ -55,11 +55,37 @@ const createRestyleFunction = < typeof themeKey === 'string' && typeof property === 'string' ) { + /* + The following code is required to ensure all variants that have different breakpoint objects are turned into unique strings. By simply retuning String(props[property]), two different variants with breakpoints will return the same string. + For example, if we have the following variant: + spacingVariant: { + defaults: {}, + noPadding: { + phone: 'none', + tablet: 'none', + }, + mediumPadding: { + phone: 'm', + tablet: 'm', + } + } + using String(props[property]) will turn both variants into [object Object], making them equivalent and resulting in separate styles being memoized into the same hash key. + By building the propertyValue string ourselves from the breakpoints, we can format the variants to be "phone:nonetablet:none" and "phone:mtablet:m" respectively, making each memoized hash key unique. + */ + let propertyValue = ''; + if (typeof props[property] === 'object') { + for (const [breakpoint, value] of Object.entries(props[property])) { + propertyValue += `${breakpoint}:${value}`; + } + } else { + propertyValue = String(props[property]); + } + return getMemoizedMapHashKey( dimensions, String(themeKey), String(property), - String(props[property]), + propertyValue, ); } else { return null; diff --git a/src/test/TestButton.tsx b/src/test/TestButton.tsx new file mode 100644 index 00000000..dab0005b --- /dev/null +++ b/src/test/TestButton.tsx @@ -0,0 +1,45 @@ +import React, {ComponentPropsWithoutRef} from 'react'; +import {Text, TouchableOpacity} from 'react-native'; + +import useRestyle from '../hooks/useRestyle'; +import {position, PositionProps} from '../restyleFunctions'; +import createVariant, {VariantProps} from '../createVariant'; +import composeRestyleFunctions from '../composeRestyleFunctions'; + +const theme = { + colors: {}, + spacing: {}, + buttonVariants: { + defaults: {}, + }, + breakpoints: { + phone: 0, + tablet: 376, + }, + zIndices: { + phone: 5, + }, +}; +type Theme = typeof theme; + +type Props = VariantProps & + PositionProps & + ComponentPropsWithoutRef; + +const restyleFunctions = [ + position, + createVariant({themeKey: 'buttonVariants'}), +]; + +const composedRestyleFunction = composeRestyleFunctions( + restyleFunctions, +); + +export function Button({title, ...rest}: Props & {title: string}) { + const props = useRestyle(composedRestyleFunction, rest); + return ( + + {title} + + ); +} diff --git a/src/test/TestContainer.tsx b/src/test/TestContainer.tsx new file mode 100644 index 00000000..958653f0 --- /dev/null +++ b/src/test/TestContainer.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import {View, ViewProps} from 'react-native'; + +import useRestyle from '../hooks/useRestyle'; +import createVariant, {VariantProps} from '../createVariant'; +import composeRestyleFunctions from '../composeRestyleFunctions'; +import { + backgroundColor, + BackgroundColorProps, + BorderProps, + spacing, + SpacingProps, +} from '../restyleFunctions'; + +const palette = { + purple: '#5A31F4', + green: '#099C77', + black: '#101010', + white: '#FFF', +}; + +export const theme = { + colors: { + background: palette.purple, + }, + spacing: { + none: 0, + m: 8, + }, + breakpoints: { + phone: 320, + tablet: 768, + }, + spacingVariant: { + defaults: {}, + spacingParent: { + padding: { + phone: 'none', + tablet: 'none', + }, + }, + spacingNested: { + padding: { + phone: 'm', + tablet: 'm', + }, + }, + }, +}; +type Theme = typeof theme; + +type RestyleProps = SpacingProps & + BorderProps & + BackgroundColorProps & + VariantProps; + +const restyleFunctions = composeRestyleFunctions([ + spacing, + backgroundColor, + createVariant({themeKey: 'spacingVariant'}), +]); + +type Props = RestyleProps & ViewProps; + +export const Container = ({children, ...rest}: Props) => { + const props = useRestyle(restyleFunctions, rest); + return {children}; +}; diff --git a/src/test/createRestyleFunction.test.ts b/src/test/createRestyleFunction.test.ts index ca168076..33f43d6c 100644 --- a/src/test/createRestyleFunction.test.ts +++ b/src/test/createRestyleFunction.test.ts @@ -4,15 +4,15 @@ import {RNStyle} from '../types'; const theme = { colors: {}, spacing: {}, - breakpoints: { - phone: 0, - tablet: 376, - }, opacities: { invisible: 0, barelyVisible: 0.1, almostOpaque: 0.9, }, + breakpoints: { + phone: 0, + tablet: 376, + }, }; const dimensions = { width: 375, diff --git a/src/test/useRestyle.test.tsx b/src/test/useRestyle.test.tsx index bc9523e3..0f2e3149 100644 --- a/src/test/useRestyle.test.tsx +++ b/src/test/useRestyle.test.tsx @@ -1,56 +1,63 @@ -import React, {ComponentPropsWithoutRef} from 'react'; -import {Text, TouchableOpacity} from 'react-native'; - -import useRestyle from '../hooks/useRestyle'; -import {position, PositionProps} from '../restyleFunctions'; -import createVariant, {VariantProps} from '../createVariant'; -import composeRestyleFunctions from '../composeRestyleFunctions'; - -const theme = { - colors: {}, - spacing: {}, - buttonVariants: { - defaults: {}, - }, - breakpoints: { - phone: 0, - tablet: 376, - }, - zIndices: { - phone: 5, - }, -}; -type Theme = typeof theme; - -type Props = VariantProps & - PositionProps & - ComponentPropsWithoutRef; - -const restyleFunctions = [ - position, - createVariant({themeKey: 'buttonVariants'}), -]; - -const composedRestyleFunction = composeRestyleFunctions( - restyleFunctions, -); - -function Button({title, ...rest}: Props & {title: string}) { - const props = useRestyle(composedRestyleFunction, rest); - return ( - - {title} - - ); -} - -function Screen() { - return