diff --git a/FabricExample/__tests__/focused-input-handler.spec.tsx b/FabricExample/__tests__/focused-input-handler.spec.tsx
new file mode 100644
index 0000000000..9619c48ce4
--- /dev/null
+++ b/FabricExample/__tests__/focused-input-handler.spec.tsx
@@ -0,0 +1,50 @@
+import '@testing-library/jest-native/extend-expect';
+import React, { useState } from 'react';
+import { runOnJS } from 'react-native-reanimated';
+import { act, render } from '@testing-library/react-native';
+
+import { FocusedInputHandler, FocusedInputTextChangedEvent, useFocusedInputHandler, useReanimatedFocusedInput } from 'react-native-keyboard-controller';
+import { Text } from 'react-native';
+
+function WhatUserTyped() {
+ const [text, setText] = useState('');
+
+ useFocusedInputHandler({
+ onChangeText: (e) => {
+ 'worklet';
+
+ runOnJS(setText)(e.text);
+ },
+ });
+
+ return {text};
+}
+
+describe('`useFocusedInputHandler` specification', () => {
+ it('should execute all handlers and change corresponding elements', () => {
+ let handlers: FocusedInputHandler = {};
+ (useFocusedInputHandler as jest.Mock).mockImplementation(
+ (handler) => (handlers = handler)
+ );
+ const onChangeText = (e: FocusedInputTextChangedEvent) => handlers.onChangeText?.(e);
+
+ const { getByTestId } = render();
+
+ expect(getByTestId('text')).toHaveTextContent('');
+ act(() => onChangeText({text: '1'}));
+
+ expect(getByTestId('text')).toHaveTextContent('1');
+
+ act(() => onChangeText({text: '12'}));
+
+ expect(getByTestId('text')).toHaveTextContent('12');
+
+ act(() => onChangeText({text: '123'}));
+
+ expect(getByTestId('text')).toHaveTextContent('123');
+
+ act(() => onChangeText({text: ''}));
+
+ expect(getByTestId('text')).toHaveTextContent('');
+ });
+});
diff --git a/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx b/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
index 584c113d67..e19d60978c 100644
--- a/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
+++ b/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
@@ -1,6 +1,10 @@
import React, { FC, useCallback } from 'react';
import { ScrollViewProps, useWindowDimensions } from 'react-native';
-import { FocusedInputLayoutChangedEvent, useReanimatedFocusedInput } from 'react-native-keyboard-controller';
+import {
+ FocusedInputLayoutChangedEvent,
+ useFocusedInputHandler,
+ useReanimatedFocusedInput
+} from 'react-native-keyboard-controller';
import Reanimated, {
interpolate,
scrollTo,
@@ -105,7 +109,7 @@ const KeyboardAwareScrollView: FC = ({
return 0;
}, [bottomOffset]);
- useReanimatedFocusedInput({
+ useFocusedInputHandler({
onChangeText: ({text}) => {
'worklet';
diff --git a/docs/docs/api/hooks/input/use-focused-input-handler.md b/docs/docs/api/hooks/input/use-focused-input-handler.md
new file mode 100644
index 0000000000..220a1b657a
--- /dev/null
+++ b/docs/docs/api/hooks/input/use-focused-input-handler.md
@@ -0,0 +1,34 @@
+---
+keywords: [react-native, react native, react-native-keyboard-controller, useFocusedInputHandler, onTextChanged, onChangeText, input interceptor, react-native-reanimated, worklet, react hook]
+---
+
+# useFocusedInputHandler
+
+`useFocusedInputHandler` is a hook that allows to intercept events from a focused `TextInput`.
+
+## Example
+
+```ts
+useFocusedInputHandler({
+ onChangeText: ({text}) => {
+ 'worklet';
+ }
+}, []);
+```
+
+### Handlers
+
+#### `onChangeText`
+
+Fires an event whenever user changes text in focused `TextInput` (i. e. adds or deletes symbols). Event has following structure:
+
+```ts
+type FocusedInputTextChangedEvent = {
+ text: string;
+};
+```
+
+This handler can be handy when you need to have an access to what user typed on a global level (i. e. when you don't have a direct access to your `TextInput`), for example:
+
+- you develop a generic component for any kind of avoidance focused inputs (i. e. `AwareScrollView`) that doesn't have an access to child `TextInputs` by design;
+- you track user activity on the screen and if there is no activity for certain period of time then you do a certain action (logout for example). If you want to reset timer when user interacts with a keyboard - usage of this hook can be a good choice.
diff --git a/docs/docs/api/hooks/input/use-reanimated-focused-input.md b/docs/docs/api/hooks/input/use-reanimated-focused-input.md
index deb774ff8d..e27c6258ac 100644
--- a/docs/docs/api/hooks/input/use-reanimated-focused-input.md
+++ b/docs/docs/api/hooks/input/use-reanimated-focused-input.md
@@ -11,7 +11,7 @@ Hook will update its value in next cases:
- when keyboard changes its size (appears, disappears, changes size because of different input mode);
- when focus was changed from one `TextInput` to another;
- when `layout` of focused input was changed;
-- when user types a text;
+- when user types a text.
:::info Events order
The value from `useReanimatedFocusedInput` will be always updated before keyboard events, so you can safely read values in `onStart` handler and be sure they are up-to-date.
@@ -22,7 +22,7 @@ The value from `useReanimatedFocusedInput` will be always updated before keyboar
The `input` property from this hook is returned as `SharedValue`. The returned data has next structure:
```ts
-type KeyboardEventData = {
+type FocusedInputLayoutChangedEvent = {
target: number; // tag of the focused TextInput
layout: { // layout of the focused TextInput
x: number; // `x` coordinate inside the parent component
@@ -38,13 +38,7 @@ type KeyboardEventData = {
## Example
```tsx
-const {input} = useReanimatedFocusedInput({
- onChangeText: ({text}) => {
- 'worklet';
-
- // ...
- }
-}, []);
+const {input} = useReanimatedFocusedInput();
```
Also have a look on [example](https://github.com/kirillzyusko/react-native-keyboard-controller/tree/main/example) app for more comprehensive usage.
diff --git a/docs/versioned_docs/version-1.9.0/api/hooks/input/use-reanimated-focused-input.md b/docs/versioned_docs/version-1.9.0/api/hooks/input/use-reanimated-focused-input.md
index 12e6133f20..d0f2a96388 100644
--- a/docs/versioned_docs/version-1.9.0/api/hooks/input/use-reanimated-focused-input.md
+++ b/docs/versioned_docs/version-1.9.0/api/hooks/input/use-reanimated-focused-input.md
@@ -21,7 +21,7 @@ The value from `useReanimatedFocusedInput` will be always updated before keyboar
The `input` property from this hook is returned as `SharedValue`. The returned data has next structure:
```ts
-type KeyboardEventData = {
+type FocusedInputLayoutChangedEvent = {
target: number; // tag of the focused TextInput
layout: { // layout of the focused TextInput
x: number; // `x` coordinate inside the parent component
diff --git a/example/__tests__/focused-input-handler.spec.tsx b/example/__tests__/focused-input-handler.spec.tsx
new file mode 100644
index 0000000000..9619c48ce4
--- /dev/null
+++ b/example/__tests__/focused-input-handler.spec.tsx
@@ -0,0 +1,50 @@
+import '@testing-library/jest-native/extend-expect';
+import React, { useState } from 'react';
+import { runOnJS } from 'react-native-reanimated';
+import { act, render } from '@testing-library/react-native';
+
+import { FocusedInputHandler, FocusedInputTextChangedEvent, useFocusedInputHandler, useReanimatedFocusedInput } from 'react-native-keyboard-controller';
+import { Text } from 'react-native';
+
+function WhatUserTyped() {
+ const [text, setText] = useState('');
+
+ useFocusedInputHandler({
+ onChangeText: (e) => {
+ 'worklet';
+
+ runOnJS(setText)(e.text);
+ },
+ });
+
+ return {text};
+}
+
+describe('`useFocusedInputHandler` specification', () => {
+ it('should execute all handlers and change corresponding elements', () => {
+ let handlers: FocusedInputHandler = {};
+ (useFocusedInputHandler as jest.Mock).mockImplementation(
+ (handler) => (handlers = handler)
+ );
+ const onChangeText = (e: FocusedInputTextChangedEvent) => handlers.onChangeText?.(e);
+
+ const { getByTestId } = render();
+
+ expect(getByTestId('text')).toHaveTextContent('');
+ act(() => onChangeText({text: '1'}));
+
+ expect(getByTestId('text')).toHaveTextContent('1');
+
+ act(() => onChangeText({text: '12'}));
+
+ expect(getByTestId('text')).toHaveTextContent('12');
+
+ act(() => onChangeText({text: '123'}));
+
+ expect(getByTestId('text')).toHaveTextContent('123');
+
+ act(() => onChangeText({text: ''}));
+
+ expect(getByTestId('text')).toHaveTextContent('');
+ });
+});
diff --git a/example/src/components/AwareScrollView/KeyboardAwareScrollView.tsx b/example/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
index 14b97881b8..7aa5ac322e 100644
--- a/example/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
+++ b/example/src/components/AwareScrollView/KeyboardAwareScrollView.tsx
@@ -1,6 +1,10 @@
import React, { FC, useCallback } from 'react';
import { ScrollViewProps, useWindowDimensions } from 'react-native';
-import { FocusedInputLayoutChangedEvent, useReanimatedFocusedInput } from 'react-native-keyboard-controller';
+import {
+ FocusedInputLayoutChangedEvent,
+ useFocusedInputHandler,
+ useReanimatedFocusedInput
+} from 'react-native-keyboard-controller';
import Reanimated, {
interpolate,
scrollTo,
@@ -105,7 +109,7 @@ const KeyboardAwareScrollView: FC = ({
return 0;
}, [bottomOffset]);
- useReanimatedFocusedInput({
+ useFocusedInputHandler({
onChangeText: ({text}) => {
'worklet';
diff --git a/jest/index.js b/jest/index.js
index 4b23331044..1f01c759d0 100644
--- a/jest/index.js
+++ b/jest/index.js
@@ -34,6 +34,7 @@ const mock = {
useGenericKeyboardHandler: jest.fn(),
useKeyboardHandler: jest.fn(),
useReanimatedFocusedInput: jest.fn().mockReturnValue(focusedInput),
+ useFocusedInputHandler: jest.fn(),
// modules
KeyboardController: {
setInputMode: jest.fn(),
diff --git a/src/hooks.ts b/src/hooks.ts
index 05d059639b..f2db29e5d8 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -64,7 +64,13 @@ export function useKeyboardController() {
return { setEnabled: context.setEnabled, enabled: context.enabled };
}
-export function useReanimatedFocusedInput(
+export function useReanimatedFocusedInput() {
+ const context = useKeyboardContext();
+
+ return { input: context.layout };
+}
+
+export function useFocusedInputHandler(
handler?: FocusedInputHandler,
deps?: DependencyList
) {
@@ -79,6 +85,4 @@ export function useReanimatedFocusedInput(
context.setInputHandlers({ [key]: undefined });
};
}, deps);
-
- return { input: context.layout };
}