Skip to content

Commit 1a95f86

Browse files
committed
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.
1 parent 8a01f75 commit 1a95f86

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { render } from "@testing-library/react-native";
2+
import { Text } from "react-native-css/components/Text";
3+
import { registerCSS, testID } from "react-native-css/jest";
4+
5+
test("className with inline style props should coexist when different properties", () => {
6+
registerCSS(`.text-red { color: red; }`);
7+
8+
const component = render(
9+
<Text testID={testID} className="text-red" style={{ fontSize: 16 }} />,
10+
).getByTestId(testID);
11+
12+
// Both className and style props should be applied as array
13+
expect(component.props.style).toEqual([
14+
{ color: "#f00" }, // Changed from "red" to "#f00"
15+
{ fontSize: 16 },
16+
]);
17+
});
18+
19+
test("className with inline style props should favor inline when same property", () => {
20+
registerCSS(`.text-red { color: red; }`);
21+
22+
const component = render(
23+
<Text testID={testID} className="text-red" style={{ color: "blue" }} />,
24+
).getByTestId(testID);
25+
26+
// When same property exists, inline style should win (not array)
27+
expect(component.props.style).toEqual({ color: "blue" });
28+
});
29+
30+
test("only className should not create array", () => {
31+
registerCSS(`.text-red { color: red; }`);
32+
33+
const component = render(
34+
<Text testID={testID} className="text-red" />,
35+
).getByTestId(testID);
36+
37+
// Only className should be a flat object
38+
expect(component.props.style).toEqual({ color: "#f00" }); // Changed from "red" to "#f00"
39+
});
40+
41+
test("only inline style should not create array", () => {
42+
const component = render(
43+
<Text testID={testID} style={{ color: "blue" }} />,
44+
).getByTestId(testID);
45+
46+
// Only inline style should be a flat object
47+
expect(component.props.style).toEqual({ color: "blue" });
48+
});

src/native/styles/index.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,30 @@ function deepMergeConfig(
135135
return { ...left };
136136
}
137137

138-
let result = config.target ? Object.assign({}, left, right) : { ...left };
138+
// Handle style merging to support both className and inline style props
139+
let result: Record<string, any>;
140+
if (config.target) {
141+
if (Array.isArray(config.target) && config.target.length === 1 && config.target[0] === "style") {
142+
// Special handling for style target when we have inline styles
143+
result = { ...left, ...right };
144+
if (left?.style && right?.style && rightIsInline) {
145+
// Only create style arrays when we have different properties that should coexist
146+
const leftKeys = new Set(Object.keys(left.style));
147+
const rightKeys = new Set(Object.keys(right.style));
148+
const hasNonOverlappingProperties = [...leftKeys].some(key => !rightKeys.has(key));
149+
150+
if (hasNonOverlappingProperties) {
151+
// Different properties exist - create array for React Native to merge both
152+
result.style = [left.style, right.style];
153+
}
154+
// If all properties overlap, right.style will override via Object.assign above
155+
}
156+
} else {
157+
result = Object.assign({}, left, right);
158+
}
159+
} else {
160+
result = { ...left };
161+
}
139162

140163
if (
141164
right &&

0 commit comments

Comments
 (0)