skip to Main Content

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

Bug screenshot

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


  1. Chosen as BEST ANSWER

    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

     "android": {
          "softwareKeyboardLayoutMode": "pan"
        },
    

    This fixed problem with the keyboard and bottom sheet also. Didn't see it appear anymore.


  2. 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.

      React.useEffect(() => {
    
        let keyboardHideListener = Keyboard.addListener('keyboardDidHide', () => {
          if (isBottomSheetOpen.current) {
            bottomSheetRef.current?.snapToIndex(0);
          }
        });
    
        let keyboardShowListener = Keyboard.addListener('keyboardDidShow', () => {
          if (isBottomSheetOpen.current) {
            bottomSheetRef.current?.snapToIndex(1);
          }
        });
    
        return () => {
          keyboardHideListener.remove();
          keyboardShowListener.remove();
        };
    
      }, []);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search