Skip to content

Commit b91f6d9

Browse files
committed
fix: rules which set variables causing remounts
1 parent 2e4c95f commit b91f6d9

File tree

5 files changed

+97
-51
lines changed

5 files changed

+97
-51
lines changed

src/runtime/native/__tests__/container-queries.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,45 @@ import { registerCSS } from "react-native-css/jest";
55
const parentID = "parent";
66
const childID = "child";
77

8+
test("Unnamed containers", () => {
9+
registerCSS(`
10+
:root, :host {
11+
--color-white: #fff;
12+
}
13+
.\\@container {
14+
container-type: inline-size;
15+
}
16+
.\\@sm\\:text-white {
17+
@container (width >= 24rem) {
18+
color: var(--color-white);
19+
}
20+
}
21+
`);
22+
23+
render(
24+
<View testID={parentID} className="@container">
25+
<View testID={childID} className="@sm:text-white" />
26+
</View>,
27+
);
28+
29+
const parent = screen.getByTestId(parentID);
30+
const child = screen.getByTestId(childID);
31+
32+
expect(child).toHaveStyle(undefined);
33+
34+
// Jest does not fire layout events, so we need to manually
35+
fireEvent(parent, "layout", {
36+
nativeEvent: {
37+
layout: {
38+
width: 500,
39+
height: 200,
40+
},
41+
},
42+
});
43+
44+
expect(child).toHaveStyle({ color: "#fff" });
45+
});
46+
847
test("container query width", () => {
948
registerCSS(`
1049
.container {

src/runtime/native/__tests__/grouping.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ const childID = "child";
1010
jest.useFakeTimers();
1111

1212
test("groups", () => {
13-
registerCSS(
14-
`.group\\/item .my-class {
13+
registerCSS(`
14+
.group\\/item .my-class {
1515
color: red;
16-
}`,
17-
);
16+
}
17+
`);
1818

19-
const { rerender, getByTestId } = render(
20-
<View testID={parentID} className="group/item">
19+
render(
20+
<View testID={parentID} className="group/item will-change-container">
2121
<View testID={childID} className="my-class" />
2222
</View>,
2323
);
2424

25-
const component = getByTestId(childID);
25+
const component = screen.getByTestId(childID);
2626

2727
expect(component.props.style).toStrictEqual({ color: "#f00" });
2828

29-
rerender(
30-
<View testID={parentID}>
29+
screen.rerender(
30+
<View testID={parentID} className="will-change-container">
3131
<View testID={childID} className="my-class" />
3232
</View>,
3333
);

src/runtime/native/__tests__/upgrading.test.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ test("adding a group", () => {
4141

4242
expect(log.mock.calls).toEqual([
4343
[
44-
"ReactNativeCss: className 'group' added a container after the initial render. This causes the components state to be reset and all children be re-mounted. This will cause unexpected behavior. Use the className 'will-change-container' to avoid this warning. If this was caused by sibling components being added/removed, use a 'key' prop so React can track the component correctly.",
44+
"ReactNativeCss: className 'group' added or removed a container after the initial render. This causes the components state to be reset and all children be re-mounted. This will cause unexpected behavior. Use the className 'will-change-container' to avoid this warning. If this was caused by sibling components being added/removed, use a 'key' prop so React can track the component correctly.",
4545
],
4646
]);
4747
});
4848

49-
test.only("will-change-container", () => {
49+
test("will-change-container", () => {
5050
registerCSS(
5151
`.group .my-class {
5252
color: red;
@@ -69,9 +69,6 @@ test.only("will-change-container", () => {
6969
</View>,
7070
);
7171

72-
expect(log.mock.calls).toEqual([
73-
[
74-
"ReactNativeCss: className 'group' added or removed a container after the initial render. This causes the components state to be reset and all children be re-mounted. This will cause unexpected behavior. Use the className 'will-change-container' to avoid this warning. If this was caused by sibling components being added/removed, use a 'key' prop so React can track the component correctly.",
75-
],
76-
]);
72+
// There shouldn't be any error, as we continued to have a container
73+
expect(log.mock.calls).toEqual([]);
7774
});

src/runtime/native/injection.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
ReactNativeCssStyleSheet,
44
StyleRuleSet,
55
} from "../../compiler";
6+
import { DEFAULT_CONTAINER_NAME } from "./conditions/container-query";
67
import {
78
family,
89
observable,
@@ -27,6 +28,36 @@ StyleCollection.keyframes = family<string, Observable<Animation_V2[1]>>(() => {
2728
StyleCollection.inject = function (options: ReactNativeCssStyleSheet) {
2829
observableBatch.current = new Set();
2930

31+
StyleCollection.styles("will-change-variable").set([
32+
{
33+
s: [0],
34+
v: [],
35+
},
36+
]);
37+
38+
StyleCollection.styles("will-change-container").set([
39+
{
40+
s: [0],
41+
c: [DEFAULT_CONTAINER_NAME],
42+
},
43+
]);
44+
45+
StyleCollection.styles("will-change-animation").set([
46+
{
47+
s: [0],
48+
a: true,
49+
},
50+
]);
51+
52+
StyleCollection.styles("will-change-pressable").set([
53+
{
54+
s: [0],
55+
p: {
56+
h: 1,
57+
},
58+
},
59+
]);
60+
3061
if (options.s) {
3162
for (const style of options.s) {
3263
StyleCollection.styles(style[0]).set(style[1]);
@@ -99,33 +130,3 @@ function isDeepEqual(a: unknown, b: unknown): boolean {
99130

100131
return true;
101132
}
102-
103-
StyleCollection.styles("will-change-variable").set([
104-
{
105-
s: [0],
106-
v: [],
107-
},
108-
]);
109-
110-
StyleCollection.styles("will-change-container").set([
111-
{
112-
s: [0],
113-
c: [],
114-
},
115-
]);
116-
117-
StyleCollection.styles("will-change-animation").set([
118-
{
119-
s: [0],
120-
a: true,
121-
},
122-
]);
123-
124-
StyleCollection.styles("will-change-pressable").set([
125-
{
126-
s: [0],
127-
p: {
128-
h: 1,
129-
},
130-
},
131-
]);

src/runtime/native/react/rules.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
hoverFamily,
1313
VAR_SYMBOL,
1414
weakFamily,
15+
type ContainerContextValue,
1516
type VariableContextValue,
1617
} from "../reactivity";
1718
import { stylesFamily } from "../styles";
@@ -36,8 +37,8 @@ export function updateRules(
3637

3738
let usesVariables = false;
3839

39-
let variables = state.variables ? inheritedVariables : undefined;
40-
let containers = state.containers ? inheritedContainers : undefined;
40+
let variables: VariableContextValue | undefined;
41+
let containers: ContainerContextValue | undefined;
4142
const inlineVariables = new Set<InlineVariable>();
4243

4344
let animated = false;
@@ -101,6 +102,16 @@ export function updateRules(
101102

102103
usesVariables ||= Boolean(rule.dv);
103104

105+
// We do this even if the rule doesn't match so we can maintain a consistent render tree
106+
// We we need to inject React context
107+
if (rule.v) {
108+
variables ??= inheritedVariables;
109+
}
110+
111+
if (rule.c) {
112+
containers ??= inheritedContainers;
113+
}
114+
104115
if (
105116
!testRule(
106117
rule,
@@ -114,11 +125,8 @@ export function updateRules(
114125
}
115126

116127
if (rule.v) {
117-
// We're going to set a value, so we need to create a new object
118128
if (variables === inheritedVariables) {
119129
variables = { ...inheritedVariables };
120-
} else {
121-
variables ??= { ...inheritedVariables };
122130
}
123131

124132
for (const v of rule.v) {
@@ -199,6 +207,7 @@ export function updateRules(
199207
guards,
200208
animated,
201209
pressable,
210+
variables,
202211
};
203212
}
204213

0 commit comments

Comments
 (0)