From 1a95f8698589404dcddf44bca71512b28dc6d0b8 Mon Sep 17 00:00:00 2001 From: Ty Rauber Date: Mon, 27 Oct 2025 18:06:14 -0700 Subject: [PATCH 1/2] fix: allow className and style props to coexist when properties differ Previously, when both className and style props were provided, className-derived styles would be completely overwritten by inline styles due to Object.assign() behavior in deepMergeConfig(). This refined solution: - Only creates style arrays when className and inline styles have non-overlapping properties - Maintains CSS precedence rules (inline styles override className for same properties) - Preserves backward compatibility with existing behavior - Passes all existing tests while enabling the new functionality Enables combining NativeWind className styling with React Native Reanimated animated styles when they target different CSS properties. Fixes issue where className was ignored when style prop was also present, while preserving expected CSS specificity behavior. --- .../native/className-with-style.test.tsx | 48 +++++++++++++++++++ src/native/styles/index.ts | 25 +++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/native/className-with-style.test.tsx diff --git a/src/__tests__/native/className-with-style.test.tsx b/src/__tests__/native/className-with-style.test.tsx new file mode 100644 index 0000000..5ac156d --- /dev/null +++ b/src/__tests__/native/className-with-style.test.tsx @@ -0,0 +1,48 @@ +import { render } from "@testing-library/react-native"; +import { Text } from "react-native-css/components/Text"; +import { registerCSS, testID } from "react-native-css/jest"; + +test("className with inline style props should coexist when different properties", () => { + registerCSS(`.text-red { color: red; }`); + + const component = render( + , + ).getByTestId(testID); + + // Both className and style props should be applied as array + expect(component.props.style).toEqual([ + { color: "#f00" }, // Changed from "red" to "#f00" + { fontSize: 16 }, + ]); +}); + +test("className with inline style props should favor inline when same property", () => { + registerCSS(`.text-red { color: red; }`); + + const component = render( + , + ).getByTestId(testID); + + // When same property exists, inline style should win (not array) + expect(component.props.style).toEqual({ color: "blue" }); +}); + +test("only className should not create array", () => { + registerCSS(`.text-red { color: red; }`); + + const component = render( + , + ).getByTestId(testID); + + // Only className should be a flat object + expect(component.props.style).toEqual({ color: "#f00" }); // Changed from "red" to "#f00" +}); + +test("only inline style should not create array", () => { + const component = render( + , + ).getByTestId(testID); + + // Only inline style should be a flat object + expect(component.props.style).toEqual({ color: "blue" }); +}); diff --git a/src/native/styles/index.ts b/src/native/styles/index.ts index 09ab073..644a657 100644 --- a/src/native/styles/index.ts +++ b/src/native/styles/index.ts @@ -135,7 +135,30 @@ function deepMergeConfig( return { ...left }; } - let result = config.target ? Object.assign({}, left, right) : { ...left }; + // Handle style merging to support both className and inline style props + let result: Record; + if (config.target) { + if (Array.isArray(config.target) && config.target.length === 1 && config.target[0] === "style") { + // Special handling for style target when we have inline styles + result = { ...left, ...right }; + if (left?.style && right?.style && rightIsInline) { + // Only create style arrays when we have different properties that should coexist + const leftKeys = new Set(Object.keys(left.style)); + const rightKeys = new Set(Object.keys(right.style)); + const hasNonOverlappingProperties = [...leftKeys].some(key => !rightKeys.has(key)); + + if (hasNonOverlappingProperties) { + // Different properties exist - create array for React Native to merge both + result.style = [left.style, right.style]; + } + // If all properties overlap, right.style will override via Object.assign above + } + } else { + result = Object.assign({}, left, right); + } + } else { + result = { ...left }; + } if ( right && From 09dc39efcb9d73bd99a6b12a7dd0958c1ef9b474 Mon Sep 17 00:00:00 2001 From: Ty Rauber Date: Mon, 27 Oct 2025 18:38:10 -0700 Subject: [PATCH 2/2] perf: optimize style merging performance and readability Address code review feedback by improving the style merging logic: - Remove Set creation and array spreading for better performance - Add early exit when finding non-overlapping properties - Simplify logic flow making it more readable and maintainable - Maintain identical functionality while reducing computational overhead Performance improvements are especially beneficial for components with many style properties, reducing unnecessary object iterations. --- src/native/styles/index.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/native/styles/index.ts b/src/native/styles/index.ts index 644a657..0e2319a 100644 --- a/src/native/styles/index.ts +++ b/src/native/styles/index.ts @@ -138,20 +138,31 @@ function deepMergeConfig( // Handle style merging to support both className and inline style props let result: Record; if (config.target) { - if (Array.isArray(config.target) && config.target.length === 1 && config.target[0] === "style") { + if ( + Array.isArray(config.target) && + config.target.length === 1 && + config.target[0] === "style" + ) { // Special handling for style target when we have inline styles result = { ...left, ...right }; + // More performant approach - check for non-overlapping properties without Sets if (left?.style && right?.style && rightIsInline) { - // Only create style arrays when we have different properties that should coexist - const leftKeys = new Set(Object.keys(left.style)); - const rightKeys = new Set(Object.keys(right.style)); - const hasNonOverlappingProperties = [...leftKeys].some(key => !rightKeys.has(key)); - + const leftStyle = left.style; + const rightStyle = right.style; + + // Quick check: do any left properties NOT exist in right? + let hasNonOverlappingProperties = false; + for (const key in leftStyle) { + if (!(key in rightStyle)) { + hasNonOverlappingProperties = true; + break; // Early exit for performance + } + } + if (hasNonOverlappingProperties) { - // Different properties exist - create array for React Native to merge both - result.style = [left.style, right.style]; + result.style = [leftStyle, rightStyle]; } - // If all properties overlap, right.style will override via Object.assign above + // Otherwise, Object.assign above will handle the override correctly } } else { result = Object.assign({}, left, right);