Search code examples
javascriptreactjsreact-nativereact-native-navigationreact-native-state

Why is React Native state not properly being set when I tap a button in navigationOptions?


I have a React Native screen/component in which there is a menu button in the top-left corner of the nav/header. When that button is tapped, I want to make a menu within the screen/component itself toggle open and shut.

For whatever reason though, with how I have it set up now, the first time I tap the menu button, the menu opens, but after that, it always stays open and doesn't close, no matter how many times I tap the button. It seems like the state is not properly being updated or something.

Here's the basic code I have for the menu toggling:

const Screen = ({ navigation }) => {
    const [showMenu, setShowMenu] = useState(false);

    useEffect(() => {
        navigation.setParams({
            toggleMenu: () => {
                setShowMenu(!showMenu);
            }
        });
    }, []);

    return (
        <View>
            { showMenu &&
                <FlatList
                    // Menu code here.
                />
            }
            {/* Other screen rendering here. */}
        </View>
    );
};

Screen.navigationOptions = ({ navigation }) => {
    const { params } = navigation.state;

    return {
        headerLeft: () => {
            return (
                <TouchableOpacity
                    onPress={params.toggleMenu}
                >
                    <Text>Menu</Text>
                </TouchableOpacity>
            );
        }
    };
};

export default Screen;

I'm thinking that it might be some closure issue, but I'm really not sure. I added a console.log to toggleMenu, and the showMenu value is always false when I enter into the function, which then sets it to true, thus while the menu won't close.

Why the showMenu value is always false, even after I use setShowMenu to set it to true is beyond me though. Anyone have any ideas? Thank you.


Solution

  • You have a stale closure in your useEffect. Its one of the most common problems people run into with hooks. Change it to use the updater form and it should start working.

    setShowMenu(prevVal => !prevVal);
    

    showMenu is basically stuck at it's initial value inside the useEffect, so every time the update is called it is doing setShowMenu(!false). That's why it appears to work the first time.

    The updater form guarantees that you will be working with the most recent copy of state.