Skip to content

Commit c300f84

Browse files
mfkrausemeta-codesync[bot]
authored andcommitted
fix(text): avoid mutating accessibilityState (#58318)
Summary: When `accessibilityState.disabled` conflicts with an explicit `disabled` prop, the `Text` component currently updates `accessibilityState` in place. This mutates an object owned by the caller, which can cause issues when other code uses that same object. This fix creates a new object instead, while retaining existing behavior (giving priority to the explicit prop). ## Changelog: [GENERAL] [FIXED] - Prevent Text from mutating the accessibilityState prop Pull Request resolved: #58318 Test Plan: Unchanged tests pass + added a regression test Reviewed By: Abbondanzo Differential Revision: D118788803 Pulled By: javache fbshipit-source-id: 77f69e851d06250aa27e20912a2c2fc91394b81d
1 parent d44efb0 commit c300f84

2 files changed

Lines changed: 41 additions & 20 deletions

File tree

packages/react-native/Libraries/Text/Text.js

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,22 @@ const TextImpl: component(
9292
...NativeTextProps,
9393
};
9494
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
95+
const _accessibilityStateDisabled =
96+
ariaDisabled ?? accessibilityState?.disabled;
97+
const _disabled = disabled ?? _accessibilityStateDisabled;
98+
99+
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
100+
// falsy states we need to update the accessibilityState object to use the disabled prop.
101+
const needsAccessibilityStateDisabledUpdate =
102+
_disabled !== _accessibilityStateDisabled &&
103+
((_disabled != null && _disabled !== false) ||
104+
(_accessibilityStateDisabled != null &&
105+
_accessibilityStateDisabled !== false));
106+
95107
let _accessibilityState: ?TextProps['accessibilityState'] =
96108
accessibilityState;
97109
if (
110+
needsAccessibilityStateDisabledUpdate ||
98111
ariaBusy != null ||
99112
ariaChecked != null ||
100113
ariaDisabled != null ||
@@ -105,39 +118,21 @@ const TextImpl: component(
105118
_accessibilityState = {
106119
busy: ariaBusy ?? _accessibilityState.busy,
107120
checked: ariaChecked ?? _accessibilityState.checked,
108-
disabled: ariaDisabled ?? _accessibilityState.disabled,
121+
disabled: _disabled,
109122
expanded: ariaExpanded ?? _accessibilityState.expanded,
110123
selected: ariaSelected ?? _accessibilityState.selected,
111124
};
112125
} else {
113126
_accessibilityState = {
114127
busy: ariaBusy,
115128
checked: ariaChecked,
116-
disabled: ariaDisabled,
129+
disabled: _disabled,
117130
expanded: ariaExpanded,
118131
selected: ariaSelected,
119132
};
120133
}
121134
}
122135

123-
const _accessibilityStateDisabled = _accessibilityState?.disabled;
124-
const _disabled = disabled ?? _accessibilityStateDisabled;
125-
126-
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
127-
// falsy states we need to update the accessibilityState object to use the disabled prop.
128-
if (
129-
_disabled !== _accessibilityStateDisabled &&
130-
((_disabled != null && _disabled !== false) ||
131-
(_accessibilityStateDisabled != null &&
132-
_accessibilityStateDisabled !== false))
133-
) {
134-
if (_accessibilityState == null) {
135-
_accessibilityState = {disabled};
136-
} else {
137-
_accessibilityState.disabled = _disabled;
138-
}
139-
}
140-
141136
if (ariaHidden !== undefined) {
142137
processedProps.accessibilityElementsHidden = ariaHidden;
143138
if (ariaHidden === true) {

packages/react-native/Libraries/Text/__tests__/Text-itest.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,32 @@ describe('<Text>', () => {
592592
});
593593
});
594594

595+
describe('accessibilityState', () => {
596+
it('does not mutate the prop when disabled overrides it', () => {
597+
const accessibilityState: AccessibilityProps['accessibilityState'] = {
598+
disabled: false,
599+
};
600+
const root = Fantom.createRoot();
601+
602+
Fantom.runTask(() => {
603+
root.render(
604+
<Text disabled accessibilityState={accessibilityState}>
605+
{TEST_TEXT}
606+
</Text>,
607+
);
608+
});
609+
610+
expect(accessibilityState).toEqual({disabled: false});
611+
expect(
612+
root.getRenderedOutput({props: ['accessibilityState']}).toJSX(),
613+
).toEqual(
614+
<rn-paragraph accessibilityState="{disabled:true,selected:false,checked:None,busy:false,expanded:null}">
615+
{TEST_TEXT}
616+
</rn-paragraph>,
617+
);
618+
});
619+
});
620+
595621
describe('aria-hidden', () => {
596622
it('is is passed as importantForAccessibility', () => {
597623
const root = Fantom.createRoot();

0 commit comments

Comments
 (0)