I am using react-native-bottom-sheet for choosing a category and when the Amount input has the autofocus
property the bottom sheet appears on the screen. And it looks like this
The bottom sheet appears behind the keyboard, and it doesn’t follow the snap point I want, it should be at the middle of the screen but it is always slightly above the keyboard. This doesn’t happen all the time, like 2/5 tries, and only when I open this screen for the first time. The sheet should only open when I tap on the category and it should be hidden when entering the screen.
I tried to use some variable render
to check if the bottom sheet even needs to render or not, so I don’t render it at all but then I could see it just flicker for a second.
This is my bottom sheet component. Because I want to create a reusable component I used ref to pass the function for opening the bottom sheet to the parent.
const TransactionBottomSheet: React.ForwardRefRenderFunction<refProps, Props> = (props, ref) => {
const { onSelect } = props;
const sheetRef = useRef<BottomSheet>(null);
const [data, setData] = useState<Transaction[]>(categoriesData);
const [selectedCategory, setSelectedCategory] = useState<Category | null>(null);
const openSheet = useCallback(() => {
sheetRef.current?.expand();
}, []);
const closeSheet = useCallback(() => {
sheetRef.current?.close();
}, []);
useImperativeHandle(ref, () => ({
openSheet: () => openSheet(),
}));
const setTypeData = (id: number) => {
const types = transactionCategories[id].types ?? [];
setData(Object.values(types));
};
const clearCategory = () => {
setData(categoriesData);
setSelectedCategory(null);
};
const onRowPress = (item: Transaction | Category) => {
if (!selectedCategory) {
setTypeData(item.id);
setSelectedCategory(item as Category);
} else {
onSelect(selectedCategory, item);
closeSheet();
}
};
const onClose = () => {
setData(categoriesData);
setSelectedCategory(null);
};
const renderItem = ({ item }: { item: Transaction | Category }) => (
<TransactionRowSelect item={item} hideIcon={!!selectedCategory} onPress={onRowPress} />
);
const renderBackdrop = useCallback(
(props) => <BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />,
[]
);
return (
<BottomSheet
ref={sheetRef}
snapPoints={snapPoints}
enablePanDownToClose
index={-1}
onClose={onClose}
backdropComponent={renderBackdrop}
handleStyle={styles.handle}
>
<Label style={styles.title}>{"Pick category"}</Label>
{!!selectedCategory && (
<>
<TransactionRowSelect item={selectedCategory} onPress={clearCategory} />
<Separator />
</>
)}
<BottomSheetFlatList
data={data}
keyExtractor={(item) => `${item.id}`}
renderItem={renderItem}
/>
</BottomSheet>
);
};
const styles = StyleSheet.create({
title: {
textAlign: "center",
fontSize: 16,
fontWeight: "bold",
paddingBottom: 10,
backgroundColor: colors.grey3,
},
handle: {
backgroundColor: colors.grey3,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
},
});
export default React.forwardRef(TransactionBottomSheet);
And this is Add transaction screen
const TransactionForm: React.FC<Props> = ({ navigation }) => {
const [date, setDate] = useState(new Date());
const sheetRef = useRef<TransactionBottomSheetType>(null);
const [category, setCategory] = useState<Category | null>(null);
const [type, setType] = useState<Transaction | null>(null);
const [amount, setAmount] = useState("");
const [description, setDescription] = useState("");
const [tryCreateNewTransaction, { isLoading }] = useCreateNewTransactionMutation();
const onAdd = async () => {
Keyboard.dismiss();
try {
if (type && category) {
await tryCreateNewTransaction({
amount: Number(amount),
description,
date: formatIsoDate(date),
user_id: 1,
type_id: type.id,
category_id: category.id,
}).unwrap();
navigation.goBack();
}
} catch (error) {
Alert.alert("An error occurred while adding transaction", "Please try again");
}
};
const onSelectCategory = (category: Category, type: Transaction) => {
setCategory(category);
setType(type);
};
const setCategoryText = () => {
if (!category && !type) {
return "";
}
return `${category?.label}, ${type?.label}`;
};
const openSheet = () => {
if (sheetRef?.current) {
Keyboard.dismiss();
sheetRef?.current?.openSheet();
}
};
return (
<View style={styles.container}>
<DatePickerInput date={date} maximumDate={new Date()} onDateSelect={setDate} />
// This is just TextInput component with some custom style, here is the autofocus props
<LabelInput
value={amount}
placeholder='Amount'
onChangeText={setAmount}
keyboardType='decimal-pad'
style={styles.marginTop}
icon={<FontAwesome5 name='coins' size={24} color={colors.greenMint} />}
autoFocus
/>
{/* <InputErrorLabel text={errors.amount} isVisible={!!errors.amount} /> */}
<TouchableOpacity onPress={openSheet}>
<LabelInput
value={setCategoryText()}
icon={<MaterialIcons name='category' size={24} color={colors.greenMint} />}
disabled
placeholder='Category'
style={styles.marginTop}
inputStyle={styles.category}
/>
</TouchableOpacity>
<TextBox
placeholder='Transaction comment'
style={styles.marginTop}
numberOfLines={6}
maxLength={300}
value={description}
onChangeText={setDescription}
/>
<CustomButton title='Submit' onPress={onAdd} style={styles.marginTop} />
<AppActivityIndicator isLoading={isLoading} />
<TransactionBottomSheet ref={sheetRef} onSelect={onSelectCategory} />
</View>
);
};
export default TransactionForm;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 16,
paddingTop: 20,
},
marginTop: {
marginTop: 20,
},
category: {
color: colors.black,
},
});
2
Answers
Found a fix for the problem, it looks like it had to do with the keyboard and expo. I noticed that when the keyboard is shown or dismissed there is a white background showing behind it for a split second. I fixed this by adding this to
app.json
This fixed problem with the keyboard and bottom sheet also. Didn't see it appear anymore.
Listen to the keyboard
show/hide
and do whatever you want with the bottom sheet.for example, here I’m changing the snapping point of the bottom sheet.