Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 449
Yash Gupta Avatar asked May 10 '20 09:05

Yash Gupta


People also ask

Why does the React useState hook not update immediately?

The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.

How do I update my state hook?

To update the state, call the state updater function with the new state setState(newState) . Alternatively, if you need to update the state based on the previous state, supply a callback function setState(prevState => newState) .

For what purpose is the useCallback () hook used?

The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.


Video Answer


1 Answers

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)

like image 170
Shubham Khatri Avatar answered Oct 22 '22 02:10

Shubham Khatri