Skip to content

Commit b27ed8a

Browse files
authored
feat: useKeyboardState (#894)
## 📜 Description Added new `useKeyboardState` hook. ## 💡 Motivation and Context Turns out we don't have a pretty basic functionality - checking keyboard state. So in this PR I'm adding `useKeyboardState` hook. We already have `KeyboardController.state()` method, which can be used inside callbacks. The new hook acts as a reactive function (i. e. re-renders when data changes). I've been thinking on how would be better implement this hook. Initial considerations were: - should we store data in context to make sure all dependant children gets re-rendered simultaneously; - should we use proxy/selectors to react on partial object changes and re-render only then; For both answers current answer is "no". Those are premature optimizations and I'd like to start with something simple. Later I can bring those optimizations in the codebase but for now we simply make `.state()` reactive. Also in this PR I made `state` always returning value. I think it'll be annoying for devs to write `?.` everywhere, so starting from now this method always returns state. Closes #893 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### Docs - added page about `useKeyboardState`; ### JS - make `KeyboardController.state()` non-nullable; - added `useKeyboardState` hook; - added new mocks and new unit-tests. ## 🤔 How Has This Been Tested? Tested in example app. ## 📸 Screenshots (if appropriate): ![image](https://github.com/user-attachments/assets/731ac502-9622-45de-b279-8bd116942068) ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 8c1e53b commit b27ed8a

File tree

21 files changed

+326
-6
lines changed

21 files changed

+326
-6
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { renderHook } from "@testing-library/react-native";
2+
import { useKeyboardState } from "react-native-keyboard-controller";
3+
4+
describe("`useKeyboardState` specification", () => {
5+
it("should return mocked state", () => {
6+
const { result } = renderHook(() => useKeyboardState());
7+
8+
expect(result.current).toStrictEqual({
9+
isVisible: false,
10+
height: 0,
11+
duration: 0,
12+
timestamp: 0,
13+
target: 0,
14+
type: "default",
15+
appearance: "default",
16+
});
17+
});
18+
});

FabricExample/src/constants/screenNames.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export enum ScreenNames {
2323
BOTTOM_TAB_BAR = "BOTTOM_TAB_BAR",
2424
OVER_KEYBOARD_VIEW = "OVER_KEYBOARD_VIEW",
2525
IMAGE_GALLERY = "IMAGE_GALLERY",
26+
USE_KEYBOARD_STATE = "USE_KEYBOARD_STATE",
2627
}

FabricExample/src/navigation/ExamplesStack/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import InteractiveKeyboard from "../../screens/Examples/InteractiveKeyboard";
1313
import InteractiveKeyboardIOS from "../../screens/Examples/InteractiveKeyboardIOS";
1414
import KeyboardAnimation from "../../screens/Examples/KeyboardAnimation";
1515
import KeyboardAvoidingViewExample from "../../screens/Examples/KeyboardAvoidingView";
16+
import UseKeyboardState from "../../screens/Examples/KeyboardStateHook";
1617
import LottieAnimation from "../../screens/Examples/Lottie";
1718
import ModalExample from "../../screens/Examples/Modal";
1819
import NonUIProps from "../../screens/Examples/NonUIProps";
@@ -46,6 +47,7 @@ export type ExamplesStackParamList = {
4647
[ScreenNames.BOTTOM_TAB_BAR]: undefined;
4748
[ScreenNames.OVER_KEYBOARD_VIEW]: undefined;
4849
[ScreenNames.IMAGE_GALLERY]: undefined;
50+
[ScreenNames.USE_KEYBOARD_STATE]: undefined;
4951
};
5052

5153
const Stack = createStackNavigator<ExamplesStackParamList>();
@@ -115,6 +117,9 @@ const options = {
115117
[ScreenNames.IMAGE_GALLERY]: {
116118
title: "Image gallery",
117119
},
120+
[ScreenNames.USE_KEYBOARD_STATE]: {
121+
title: "useKeyboardState",
122+
},
118123
};
119124

120125
const ExamplesStack = () => (
@@ -224,6 +229,11 @@ const ExamplesStack = () => (
224229
name={ScreenNames.IMAGE_GALLERY}
225230
options={options[ScreenNames.IMAGE_GALLERY]}
226231
/>
232+
<Stack.Screen
233+
component={UseKeyboardState}
234+
name={ScreenNames.USE_KEYBOARD_STATE}
235+
options={options[ScreenNames.USE_KEYBOARD_STATE]}
236+
/>
227237
</Stack.Navigator>
228238
);
229239

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from "react";
2+
import { StyleSheet, Text, TextInput } from "react-native";
3+
import {
4+
useKeyboardState,
5+
useResizeMode,
6+
} from "react-native-keyboard-controller";
7+
8+
const styles = StyleSheet.create({
9+
input: {
10+
height: 50,
11+
width: "100%",
12+
backgroundColor: "#3c3c3c",
13+
},
14+
text: {
15+
color: "black",
16+
},
17+
});
18+
19+
export default function UseKeyboardState() {
20+
useResizeMode();
21+
22+
const state = useKeyboardState();
23+
24+
return (
25+
<>
26+
<TextInput
27+
keyboardAppearance="dark"
28+
keyboardType="email-address"
29+
style={styles.input}
30+
/>
31+
<Text style={styles.text}>isVisible: {state.isVisible.toString()}</Text>
32+
<Text style={styles.text}>height: {state.height}</Text>
33+
<Text style={styles.text}>duration: {state.duration}</Text>
34+
<Text style={styles.text}>timestamp: {state.timestamp}</Text>
35+
<Text style={styles.text}>target: {state.target}</Text>
36+
<Text style={styles.text}>type: {state.type}</Text>
37+
<Text style={styles.text}>appearance: {state.appearance}</Text>
38+
</>
39+
);
40+
}

FabricExample/src/screens/Examples/Main/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,10 @@ export const examples: Example[] = [
129129
info: ScreenNames.IMAGE_GALLERY,
130130
icons: "🖼️",
131131
},
132+
{
133+
title: "useKeyboardState",
134+
testID: "use_keyboard_State",
135+
info: ScreenNames.USE_KEYBOARD_STATE,
136+
icons: "📝",
137+
},
132138
];

docs/docs/api/hooks/keyboard/use-keyboard-animation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ keywords:
66
react-native animated,
77
react hook,
88
]
9+
sidebar_position: 1
910
---
1011

1112
# useKeyboardAnimation

docs/docs/api/hooks/keyboard/use-keyboard-handler/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ keywords:
1111
worklet,
1212
react hook,
1313
]
14+
sidebar_position: 3
1415
---
1516

1617
import CodeBlock from "@theme/CodeBlock";
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
keywords: [react-native-keyboard-controller, useKeyboardState, react hook]
3+
sidebar_position: 4
4+
---
5+
6+
# useKeyboardState
7+
8+
`useKeyboardState` is a hook which gives an access to current keyboard state.
9+
10+
:::warning
11+
Use this hook only when you need to control `props` of views returned in JSX-markup. If you need to access the keyboard `state` in callbacks or event handlers then consider to use [KeyboardController.state()](../../keyboard-controller.md#state) method instead. This allows you to retrieve values as needed without triggering unnecessary re-renders.
12+
13+
<div className="code-grid">
14+
<div className="code-block">
15+
16+
```tsx title="✅ Recommended 👍"
17+
// use KeyboardController.state()
18+
19+
<Button
20+
onPress={() => {
21+
const state = KeyboardController.state();
22+
if (state.isVisible) {
23+
// ...
24+
}
25+
}}
26+
>
27+
Go to Next Page
28+
</Button>
29+
```
30+
31+
</div>
32+
<div className="code-block">
33+
34+
```tsx title="❌ Not recommended 🙅‍♂️"
35+
const { isVisible } = useKeyboardState();
36+
37+
<Button
38+
onPress={() => {
39+
// don't consume state from hook
40+
if (isVisible) {
41+
// ...
42+
}
43+
}}
44+
>
45+
Go to next Page
46+
</Button>;
47+
```
48+
49+
</div>
50+
</div>
51+
52+
:::
53+
54+
## Data structure
55+
56+
`useKeyboardState` returns a [`KeyboardState`](../../keyboard-controller.md#state) object.
57+
58+
## Example
59+
60+
```tsx
61+
import { View, Text, StyleSheet } from "react-native";
62+
import { useKeyboardState } from "react-native-keyboard-controller";
63+
64+
const ShowcaseComponent = () => {
65+
const { isVisible } = useKeyboardState();
66+
67+
return (
68+
<View style={isVisible ? styles.highlighted : null}>
69+
<Text>Address form</Text>
70+
</View>
71+
);
72+
};
73+
74+
const styles = StyleSheet.create({
75+
highlighted: {
76+
borderColor: "#0070D8",
77+
},
78+
});
79+
```
80+
81+
Also have a look on [example](https://github.com/kirillzyusko/react-native-keyboard-controller/tree/main/example) app for more comprehensive usage.

docs/docs/api/hooks/keyboard/use-reanimated-keyboard-animation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ keywords:
66
react-native-reanimated,
77
react hook,
88
]
9+
sidebar_position: 2
910
---
1011

1112
# useReanimatedKeyboardAnimation

docs/docs/api/keyboard-controller.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,25 @@ if (KeyboardController.isVisible()) {
9393
### `state`
9494

9595
```ts
96-
static state(): KeyboardEventData | null;
96+
static state(): KeyboardState;
9797
```
9898

9999
This method returns the last keyboard state. It returns `null` if keyboard was not shown in the app yet.
100100

101+
The `KeyboardState` is represented by following structure:
102+
103+
```ts
104+
type KeyboardState = {
105+
isVisible: boolean;
106+
height: number;
107+
duration: number; // duration of the animation
108+
timestamp: number; // timestamp of the event from native thread
109+
target: number; // tag of the focused `TextInput`
110+
type: string; // `keyboardType` property from focused `TextInput`
111+
appearance: string; // `keyboardAppearance` property from focused `TextInput`
112+
};
113+
```
114+
101115
### `setFocusTo`
102116

103117
```ts

0 commit comments

Comments
 (0)