Search code examples
javascriptreactjsreact-nativereact-hooksusecallback

useCallback Hooks getting old state values and not updating


My Callback returning same state after calling it again and again

i am new to react hooks in react classes i could have used shouldcomponentupdate and could have solved this issue

but there isn't params in usecallback hook

import React, {
  useLayoutEffect,
  useState,
  useCallback,
  useEffect,
} from "react";
import { StyleSheet, Text, View, Platform, YellowBox } from "react-native";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import HeaderButton from "../components/HeaderButton";
import { Switch } from "react-native-paper";
import Colors from "../constants/Colors";
//check use callback value first

YellowBox.ignoreWarnings([
  "Non-serializable values were found in the navigation state",
]);

const FilterSwitch = ({ label, state, onChange }) => (
  <View style={styles.filterContainer}>
    <Text>{label}</Text>
    <Switch
      value={state}
      trackColor={{ true: Colors.primaryColor }}
      thumbColor={Platform.OS === "android" ? Colors.primaryColor : ""}
      onValueChange={onChange}
    />
  </View>
);

const FiltersScreen = ({ navigation }) => {
  const [isGlutenFree, setIsGlutenFree] = useState(false);
  const [isLactoseFree, setIsLactoseFree] = useState(false);
  const [isVegan, setIsVegan] = useState(false);
  const [isVegetarian, setIsVegetarian] = useState(false);

  const saveFilters = useCallback(() => {
    console.log(isGlutenFree);
    const appliedFilters = {
      glutenFree: isGlutenFree,
      lactoseFree: isLactoseFree,
      vegan: isVegan,
      isVegetarian: isVegetarian,
    };

    console.log(appliedFilters);
  }, [isGlutenFree, isLactoseFree, isVegan, isVegetarian]);


  useLayoutEffect(() => {
    navigation.setOptions({
      headerTitle: "Filter Meals",
      headerLeft: () => (
        <HeaderButtons HeaderButtonComponent={HeaderButton}>
          <Item
            title="Menu"
            iconName="ios-menu"
            onPress={() => {
              navigation.toggleDrawer();
            }}
          />
        </HeaderButtons>
      ),
      headerRight: () => (
        <HeaderButtons HeaderButtonComponent={HeaderButton}>
          <Item
            title="Save"
            iconName="ios-save"
            onPress={() => saveFilters()}
          />
        </HeaderButtons>
      ),
    });
  }, [navigation]);
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>Available Filters / Restrictions</Text>
      <FilterSwitch
        label="Gluten Free"
        state={isGlutenFree}
        onChange={(newValue) => {
          setIsGlutenFree(newValue);
        }}
      />
      <FilterSwitch
        label="Lactos Free"
        state={isLactoseFree}
        onChange={(newValue) => setIsLactoseFree(newValue)}
      />
      <FilterSwitch
        label="Vegan Free"
        state={isVegan}
        onChange={(newValue) => setIsVegan(newValue)}
      />
      <FilterSwitch
        label="Vegetarian Free"
        state={isVegetarian}
        onChange={(newValue) => setIsVegetarian(newValue)}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: "center",
  },
  filterContainer: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    width: "80%",
    marginVertical: 15,
  },
  title: {
    fontFamily: "open-sans-bold",
    fontSize: 22,
    margin: 20,
    textAlign: "center",
  },
});
export default FiltersScreen;

How can i solve this problem? I Have read official docs and i couldn't find any related issues there


Solution

  • The issue with your code is that, even though you have provided useCallback with all dependecy array, you are only using the first closure value of the function within onPress={() => saveFilters()} since this code is executed inside useLayoutEffect and that is run only on navigation change

    The solution here is to update navigation options on both navigation change and on saveFilters change

    useLayoutEffect(() => {
        navigation.setOptions({
          headerTitle: "Filter Meals",
          headerLeft: () => (
            <HeaderButtons HeaderButtonComponent={HeaderButton}>
              <Item
                title="Menu"
                iconName="ios-menu"
                onPress={() => {
                  navigation.toggleDrawer();
                }}
              />
            </HeaderButtons>
          ),
          headerRight: () => (
            <HeaderButtons HeaderButtonComponent={HeaderButton}>
              <Item
                title="Save"
                iconName="ios-save"
                onPress={() => saveFilters()}
              />
            </HeaderButtons>
          ),
        });
      }, [navigation, saveFilters]);
    

    P.S. This is the kind of situation where I feel hooks implementation sometimes becomes hacky or inefficient. Also debugging closures is way more difficult then debugging context(this in class component)