Skip to content

Commit f1bcad7

Browse files
committed
fix: memoize text input events
1 parent 3176ef9 commit f1bcad7

File tree

5 files changed

+95
-30
lines changed

5 files changed

+95
-30
lines changed

@types/react-native.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare module 'react-native/Libraries/vendor/emitter/EventEmitter' {
2+
import {EventEmitter} from 'react-native';
3+
4+
const EventEmitter: EventEmitter;
5+
6+
export = EventEmitter;
7+
}

src/KeyboardAwareContainer.tsx

+15-13
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,14 @@ export function useKeyboardAwareContainerProps<
285285
}, [updateOffsets]);
286286

287287
useEffect(() => {
288-
const sub = hijackTextInputEvents({
289-
// We watch for the switch between two text inputs and update offsets
290-
// accordingly.
291-
// A switch between two text inputs happens when a keyboard is shown
292-
// and another text input is currently being focused on.
293-
onFocusTextInput: newFocusedTextInputNodeHandle => {
288+
const textInputEvents = hijackTextInputEvents();
289+
// We watch for the switch between two text inputs and update offsets
290+
// accordingly.
291+
// A switch between two text inputs happens when a keyboard is shown
292+
// and another text input is currently being focused on.
293+
const sub = textInputEvents.addListener(
294+
'textInputDidFocus',
295+
newFocusedTextInputNodeHandle => {
294296
requestAnimationFrame(async () => {
295297
if (
296298
!keyboardLayoutRef.current ||
@@ -315,7 +317,7 @@ export function useKeyboardAwareContainerProps<
315317
updateOffsets();
316318
});
317319
},
318-
});
320+
);
319321

320322
return () => {
321323
sub.remove();
@@ -348,13 +350,13 @@ export function useKeyboardAwareContainerProps<
348350
const flatStyleProp = StyleSheet.flatten(styleProp) || {};
349351

350352
let scrollViewBottomInsetProp = 0;
351-
if ('paddingBottom' in flatStyleProp) {
352-
if (typeof flatStyleProp.paddingBottom === 'number') {
353-
scrollViewBottomInsetProp = flatStyleProp.paddingBottom;
353+
if ('marginBottom' in flatStyleProp) {
354+
if (typeof flatStyleProp.marginBottom === 'number') {
355+
scrollViewBottomInsetProp = flatStyleProp.marginBottom;
354356
}
355-
} else if ('padding' in flatStyleProp) {
356-
if (typeof flatStyleProp.padding === 'number') {
357-
scrollViewBottomInsetProp = flatStyleProp.padding;
357+
} else if ('margin' in flatStyleProp) {
358+
if (typeof flatStyleProp.margin === 'number') {
359+
scrollViewBottomInsetProp = flatStyleProp.margin;
358360
}
359361
}
360362

src/utils/EventEmitter.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
EventEmitterListener,
3+
EmitterSubscription as EventEmitterSubscription,
4+
EventEmitter as WeaklyTypedEventEmitterInstance,
5+
} from 'react-native';
6+
import WeaklyTypedEventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
7+
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
export type ListenerType<T> = [T] extends [(...args: infer U) => any]
10+
? U
11+
: [T] extends [void]
12+
? []
13+
: [T];
14+
15+
export interface EventEmitterInstance<TEvents>
16+
extends Omit<
17+
WeaklyTypedEventEmitterInstance,
18+
| 'addListener'
19+
| 'once'
20+
| 'removeAllListeners'
21+
| 'listeners'
22+
| 'emit'
23+
| 'removeListener'
24+
> {
25+
addListener<K extends keyof TEvents>(
26+
eventType: K,
27+
listener: (...args: ListenerType<TEvents[K]>) => void,
28+
context?: unknown,
29+
): EventEmitterSubscription;
30+
once<K extends keyof TEvents>(
31+
eventType: K,
32+
listener: (...args: ListenerType<TEvents[K]>) => void,
33+
context?: unknown,
34+
): EventEmitterSubscription;
35+
removeAllListeners(eventType: keyof TEvents): void;
36+
listeners(eventType: keyof TEvents): EventEmitterSubscription[];
37+
emit<K extends keyof TEvents>(
38+
eventType: K,
39+
...params: ListenerType<TEvents[K]>
40+
): void;
41+
removeListener<K extends keyof TEvents>(
42+
eventType: K,
43+
listener: (...args: ListenerType<TEvents[K]>) => void,
44+
): void;
45+
}
46+
47+
export type EventEmitter<TEvents> = EventEmitterInstance<TEvents>;
48+
export type EventEmitterListener = EventEmitterListener;
49+
50+
export const EventEmitter = (WeaklyTypedEventEmitter as unknown) as {
51+
new <TEvents>(): EventEmitterInstance<TEvents>;
52+
};

src/utils/hijackTextInputEvents.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
// @ts-ignore: internal module
22
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';
3+
import {EventEmitter} from './EventEmitter';
4+
5+
interface TextInputEvents {
6+
textInputDidFocus: (focusedTextInputId: number) => void;
7+
textInputDidBlur: (focusedTextInputId: number) => void;
8+
}
9+
10+
let textInputEvents: EventEmitter<TextInputEvents> | null = null;
11+
12+
export function hijackTextInputEvents() {
13+
if (textInputEvents) return textInputEvents;
14+
15+
textInputEvents = new EventEmitter();
316

4-
export function hijackTextInputEvents({
5-
onFocusTextInput,
6-
onBlurTextInput,
7-
}: {
8-
onFocusTextInput?: (focusedTextInputId: number) => void;
9-
onBlurTextInput?: (focusedTextInputId: number) => void;
10-
}) {
1117
const originalFocusTextInput = TextInputState.focusTextInput.bind(
1218
TextInputState,
1319
);
@@ -25,10 +31,11 @@ export function hijackTextInputEvents({
2531
focusedTextInputId !== null
2632
) {
2733
currentlyFocusedTextInputId = focusedTextInputId;
28-
if (onFocusTextInput) onFocusTextInput(focusedTextInputId);
34+
if (textInputEvents) {
35+
textInputEvents.emit('textInputDidFocus', focusedTextInputId);
36+
}
2937
}
3038
};
31-
3239
TextInputState.blurTextInput = (focusedTextInputId: number | null) => {
3340
originalBlurTextInput(focusedTextInputId);
3441

@@ -37,14 +44,11 @@ export function hijackTextInputEvents({
3744
focusedTextInputId !== null
3845
) {
3946
currentlyFocusedTextInputId = null;
40-
if (onBlurTextInput) onBlurTextInput(focusedTextInputId);
47+
if (textInputEvents) {
48+
textInputEvents.emit('textInputDidBlur', focusedTextInputId);
49+
}
4150
}
4251
};
4352

44-
return {
45-
remove: () => {
46-
TextInputState.focusTextInput = originalFocusTextInput;
47-
TextInputState.blurTextInput = originalBlurTextInput;
48-
},
49-
};
53+
return textInputEvents;
5054
}

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
"strictPropertyInitialization": true,
1919
"target": "esnext"
2020
},
21-
"include": ["./src/**/*"]
21+
"include": ["./@types/**/*", "./src/**/*"]
2222
}

0 commit comments

Comments
 (0)