1

I am trying to create a React Native e-commerce app where the featured products are shown, but then the user can view a list of categories via a sheet popping up from the bottom, which will load the products from said category.

I have managed to create such a bottom sheet using react-native-btr's BottomSheet. However, the function to show/hide the component (simply toggling a boolean in state) needs to be available to the component itself (to handle the back button and backdrop presses).

This is the component code:

const TestNav = (props, ref) => {
  const [visible, setVisible] = useState(false);

  const toggleVisible = () => {
    setVisible(!visible);
  };

  useImperativeHandle(ref, () => toggleVisible());

  return (
    <BottomSheet
      visible={visible}
      //setting the visibility state of the bottom shee
      onBackButtonPress={toggleVisible}
      //Toggling the visibility state on the click of the back botton
      onBackdropPress={toggleVisible}
      //Toggling the visibility state on the clicking out side of the sheet
    >
      <View style={styles.bottomNavigationView}>
        <View
          style={{
            flex: 1,
            flexDirection: 'column',
          }}
        >
          {DummyData.map((item) => {
            return (
              <Button
                key={item.id}
                title={item.name}
                type="clear"
                buttonStyle={styles.button}
                onPress={() => console.log(item.name)}
              />
            );
          })}
        </View>
      </View>
    </BottomSheet>
  );
};
export default React.forwardRef(TestNav);

And here is the code for the screen where it's being used (it's called ChatScreen as I'm using it as a testing ground since I haven't implemented that feature yet)

import React, { useRef } from 'react';
import {SafeAreaView,StyleSheet,View,Text} from 'react-native';
import TestNav from '../components/TestNav';
import { Button } from 'react-native-elements';

const ChatScreen = () => {
  const childRef = useRef(null);

  const toggleBottomNavigationView = () => {
    if (myRef.current) {
      childRef.current.toggleVisible;
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.container}>
        <Text
          style={{
            fontSize: 20,
            marginBottom: 20,
            textAlign: 'center',
          }}
        >
          Content goes here
        </Text>
        <Button
          onPress={() => toggleBottomNavigationView()}
          title="Show Bottom Sheet"
        />
        <TestNav ref={childRef} />
      </View>
    </SafeAreaView>
  );
};
export default ChatScreen;

However, this code has somehow triggered an infinite loop, as I get this message:

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

How do I go about fixing this?

KiwiNFLFan
  • 59
  • 5
  • Is `myRef` a typo? I don't see where it's declared. Is the render looping occurring immediately or after some interaction, like clicking the button? – Drew Reese Apr 27 '21 at 06:09

1 Answers1

0

I think the issue lies with how you define the imperative handle. Hook callbacks are called each time the component renders and so () => toggleVisible() is called each render and creates a render loop. It should be passed a callback that returns the imperative functions/values to be made available to callees.

const toggleVisible = () => {
  setVisible(visible => !visible);
};

useImperativeHandle(ref, () => ({
  toggleVisible,
}));

In ChatScreen you then need to invoke the function. I'll assume the myRef in your snippet was a typo since it's not declared in the component and the usage appears to be similar to the guard pattern.

const toggleBottomNavigationView = () => {
  childRef.current && childRef.current.toggleVisible();
  // or childRef.current?.toggleVisible();
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181