Home > other >  React Native updated state is using initial state when calling it
React Native updated state is using initial state when calling it

Time:02-21

I just came across with the weird issue in my application. I have defined state in a functional component, I am able to update the state which i am clearly able to see through useEffect by passing it as dependency. But when i am calling the state inside handleValidatePin function it is showing the initial value.

const ChangePin=(props:Props)=>{
       const [pin, setPIN] = useState("");

        console.log("Calling", pin); //printing updated state
    
      function handleValidatePin() {
        console.log("PIN:", pin); // printing initial state [Empty String]
        setLoading(true);
        if (pin !== PIN) {
          pinInputRef.current?.shake();
          setLoading(false);
        } else {
          setTimeout(() => {
            setPINLocal(pin);
            setLoading(false);
            setToast({ type: "success", message: "PIN changed successfully!" });
            props.handleCloseSheet();
          }, 2000);
        }
      }
    
      useEffect(() => {
        console.log("Updated PIN:", pin); //printing updated state
      }, [pin]);
    return(
<View/>
  .....
</View
)

}

CodePudding user response:

function handleValidatePin() is using closure to obtain the value of pin, but updates to state are running on the React fibre. The function will only be re-created (and therefore get a new closure value) if something else causes the component to re-render.

React has useCallback() to allow the function to explicitly see the new closure value, from the the docs

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
)

So this should work (similar to useEffect, )

const handleValidatePin = useCallback(() => {
  console.log("PIN:", pin)
  ...
}, [pin]) 

Obviously, if you can pass in the pin value from the click handler, you would not need to wrap in useCallback and recreate the function,

function handleValidatePin(pin) {
  console.log("PIN:", pin)
  ...
}

In fact, you could then move the function outside the component for a small optimization (the function will never recreate on re-render).


Tested with

I ran up a basic React Native expo init app. The value of pin is always up to date inside validate() for this simple app.

import { StatusBar } from 'expo-status-bar';
import React, {useState, useEffect, useCallback} from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  const [pin, setPin] = useState()

  const validate = useCallback(() => {
    console.log('validate pin', pin)
  }, [pin])

  useEffect(() => {
    console.log('useEffect pin', pin)
  }, [pin])

  return (
    <View style={styles.container}>
      <input onChange={(e) => setPin(e.target.value)} />
      <button onClick={(e) => validate()} >Validate</button>
      <StatusBar style="auto" />
    </View>
  );
}

CodePudding user response:

I don't know if you are using your own button (custom) or built-in button (import from react-native). So I suggest that: The case you are using your own button: Make sure you do not using React.memo with areEqual function always return true or you use JSON.stringify to compare prevProps with nextProps (not expectedly when having function as props) in your customized button. Remove React.memo and see the result.

I hope it should work!

  • Related