This is a note for the implementation to dismiss the keyboard on scrolling with React Native.
First of all
I used PanResponder provided by React Native for my develpment reason. I want to replace it with react-native-gesture-handler
someday
Background
I maintain a chat app created with React Native, Expo. There was a big change in the service and there are more opportunities for users to read messages and type long text simultaneously. Then the request that they want to slide down the keyboard when they start to check message history had increased. It is a common interaction in chat apps.
There could be a published package in the world but I try to use as few packages as possible because I have the impression that JS packages become out of maintenance soon. So I started to copy the interaction with methods provided by React Native.
What I did
I used the PanResponder
provided by React Native following the docs. Especially, onPanResponderMove
is the main attribute this time.
Based on the information, I wrote the following custom hook.
import { Keyboard, PanResponder, Dimensions } from 'react-native';
import { useMemo } from 'react';
import { useKeyboard } from '@react-native-community/hooks';
const screenHeight = Dimensions.get('window').height;
export const useDismissKeyboardOnScroll = () => {
const { keyboardHeight } = useKeyboard();
const panResponder = useMemo(
() =>
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt) => {
const touchY = evt.nativeEvent.pageY;
const keyboardTop = screenHeight - keyboardHeight;
if (touchY > keyboardTop) Keyboard.dismiss();
},
}),
[keyboardHeight]
);
return panResponder.panHandlers;
};
Then I call the hook in a component and pass the value to an Animated
element.
const TestMessage = () => {
const keyboardDismissHandlers = useDismissKeyboardOnScroll();
return (
<View style={{ flex: 1 }}>
<Animated.View { ...keyboardDismissHandlers }>
<MessageHistory />
<MessageInput />
</Animated.View>
</View>
);
}
However, after testing, the function didn’t work like I expected especially when the editing text was long. The reason was quite simple. The handlers didn’t take the input height into account.
So the following snippet is the final one. I created useMessageInputContext
for this problem but this implementation depends on each project. So I’ll skip to sharing the implementation here.
import { Keyboard, PanResponder, Dimensions } from 'react-native';
import { useMemo } from 'react';
import { useKeyboard } from '@react-native-community/hooks';
import { useMessageInputContext } from 'path/to/context';
const screenHeight = Dimensions.get('window').height;
export const useDismissKeyboardOnScroll = () => {
const { keyboardHeight } = useKeyboard();
const { messageInputHeight } = useMessageInputContext();
const panResponder = useMemo(
() =>
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt) => {
const touchY = evt.nativeEvent.pageY;
const keyboardTop = screenHeight - keyboardHeight - messageInputHeight;
if (touchY > keyboardTop) Keyboard.dismiss();
},
}),
[keyboardHeight, messageInputHeight]
);
return panResponder.panHandlers;
};
That’s it!