Skip to content

Commit 14dc03e

Browse files
sbuggayfacebook-github-bot
authored andcommitted
Genalize focus/blur behavior in JS (#53829)
Summary: Pull Request resolved: #53829 Forwards along focus and blur calls to their respective native commands, leaving the TextInput path intact for now. There are a few ways places to implement this, all with different trade offs. I decided to leave TextInputState.js intact and instead only go down the original path if the focused/blurred element is a TextInput by using the registered inputs in TextInputState. This revealed a subtle issue where the TextInputs aren't registered in time for the ref callback, meaning you can't focus them there if you rely on the registered list. Changelog: [Internal] Reviewed By: shwanton Differential Revision: D82676628 fbshipit-source-id: d39c92dfc99ec4b5e100203bb43c8a4f9c80a649
1 parent 22002bb commit 14dc03e

File tree

4 files changed

+23
-6
lines changed

4 files changed

+23
-6
lines changed

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,12 @@ function InternalTextInput(props: TextInputProps): React.Node {
449449
before we can get to the long term breaking change.
450450
*/
451451
if (instance != null) {
452+
// Register the input immediately when the ref is set so that focus()
453+
// can be called from ref callbacks
454+
// Double registering during useLayoutEffect is fine, because the underlying
455+
// state is a Set.
456+
TextInputState.registerInput(instance);
457+
452458
// $FlowFixMe[prop-missing] - See the explanation above.
453459
// $FlowFixMe[unsafe-object-assign]
454460
Object.assign(instance, {

packages/react-native/Libraries/Components/View/ViewNativeComponent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const ViewNativeComponent: HostComponent<Props> =
2121
}));
2222

2323
interface NativeCommands {
24-
+focus: () => void;
25-
+blur: () => void;
24+
+focus: (viewRef: HostInstance) => void;
25+
+blur: (viewRef: HostInstance) => void;
2626
+hotspotUpdate: (viewRef: HostInstance, x: number, y: number) => void;
2727
+setPressed: (viewRef: HostInstance, pressed: boolean) => void;
2828
}

packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/ReactFabricPublicInstance-itest.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
1414
import type {HostInstance} from 'react-native';
1515

1616
import ReactNativeElement from '../../../../src/private/webapis/dom/nodes/ReactNativeElement';
17+
import TextInput from '../../../Components/TextInput/TextInput';
1718
import TextInputState from '../../../Components/TextInput/TextInputState';
1819
import View from '../../../Components/View/View';
1920
import ReactFabricHostComponent from '../ReactFabricHostComponent';
@@ -48,7 +49,7 @@ describe('ReactFabricPublicInstance', () => {
4849
const nodeRef = createRef<HostInstance>();
4950

5051
Fantom.runTask(() => {
51-
root.render(<View ref={nodeRef} />);
52+
root.render(<TextInput ref={nodeRef} />);
5253
});
5354

5455
const node = nullthrows(nodeRef.current);
@@ -73,7 +74,7 @@ describe('ReactFabricPublicInstance', () => {
7374
const ref = createRef<HostInstance>();
7475

7576
Fantom.runTask(() => {
76-
root.render(<View ref={ref} />);
77+
root.render(<TextInput ref={ref} />);
7778
});
7879

7980
const node = nullthrows(ref.current);

packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import type {InstanceHandle} from './internals/NodeInternals';
2525
import type ReactNativeDocument from './ReactNativeDocument';
2626

2727
import TextInputState from '../../../../../Libraries/Components/TextInput/TextInputState';
28+
import {Commands as ViewCommands} from '../../../../../Libraries/Components/View/ViewNativeComponent';
2829
import {create as createAttributePayload} from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload';
2930
import warnForStyleProps from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps';
31+
import * as ReactNativeFeatureFlags from '../../../featureflags/ReactNativeFeatureFlags';
3032
import {
3133
getNativeElementReference,
3234
getPublicInstanceFromInstanceHandle,
@@ -140,11 +142,19 @@ class ReactNativeElement extends ReadOnlyElement implements NativeMethods {
140142
*/
141143

142144
blur(): void {
143-
TextInputState.blurTextInput(this);
145+
if (TextInputState.isTextInput(this)) {
146+
TextInputState.blurTextInput(this);
147+
} else if (ReactNativeFeatureFlags.enableImperativeFocus()) {
148+
ViewCommands.blur(this);
149+
}
144150
}
145151

146152
focus() {
147-
TextInputState.focusTextInput(this);
153+
if (TextInputState.isTextInput(this)) {
154+
TextInputState.focusTextInput(this);
155+
} else if (ReactNativeFeatureFlags.enableImperativeFocus()) {
156+
ViewCommands.focus(this);
157+
}
148158
}
149159

150160
measure(callback: MeasureOnSuccessCallback) {

0 commit comments

Comments
 (0)