0

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
)

}

2 Answers2

0

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>
  );
}
Fody
  • 23,754
  • 3
  • 20
  • 37
  • I tried wrapping the function with useCallback but still getting the same output. I am able to get the updated state outside anywhere but not inside a function. – Himanshu Barmola Feb 20 '22 at 05:18
  • My Problem is exactly the same as https://stackoverflow.com/questions/60458187/cant-access-state-inside-function-in-react-function-component but i am still not able to get the workaround – Himanshu Barmola Feb 20 '22 at 05:28
  • It should work if pin is in the dependency list, will try it out and let you know. – Fody Feb 20 '22 at 05:29
  • Tested it, can't reproduce - sorry about that. – Fody Feb 20 '22 at 07:10
0

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!