admin管理员组

文章数量:1355658

I'm using hooks for updating state. in my code I have an AppState event listener and whenever it triggers I'm updating the appState using setAppState, however the appState inside the event listener did not change. but the value is updating outside the listener. can anyone explain why is behaving like that?

Here is my code:

    import React, { FunctionComponent, useEffect, useState } from "react"
    import { View, AppState, AppStateStatus } from "react-native"
    const Test: FunctionComponent<any> = (props: any) => {
        const [appState, setAppState] = useState < AppStateStatus > (AppState.currentState)
    
        useEffect(() => {
            AppState.addEventListener("change", _handleAppStateChange)
        },[])
    
        const _handleAppStateChange = (nextAppState: AppStateStatus) => {
         //appState not Changing here
            console.log(appState, "app state")
            if (appState.match(/inactive|background/) && nextAppState === "active") {
                console.log("App has e to the foreground!")
                activateRealtime()
            } else if (appState === "active" && nextAppState.match(/inactive|background/)) {
                console.log("App has e to background!")
            }
            setAppState(nextAppState)
        }
       //appState updated here
       console.log(appState, "app state")
        return <View />
    }

I'm using hooks for updating state. in my code I have an AppState event listener and whenever it triggers I'm updating the appState using setAppState, however the appState inside the event listener did not change. but the value is updating outside the listener. can anyone explain why is behaving like that?

Here is my code:

    import React, { FunctionComponent, useEffect, useState } from "react"
    import { View, AppState, AppStateStatus } from "react-native"
    const Test: FunctionComponent<any> = (props: any) => {
        const [appState, setAppState] = useState < AppStateStatus > (AppState.currentState)
    
        useEffect(() => {
            AppState.addEventListener("change", _handleAppStateChange)
        },[])
    
        const _handleAppStateChange = (nextAppState: AppStateStatus) => {
         //appState not Changing here
            console.log(appState, "app state")
            if (appState.match(/inactive|background/) && nextAppState === "active") {
                console.log("App has e to the foreground!")
                activateRealtime()
            } else if (appState === "active" && nextAppState.match(/inactive|background/)) {
                console.log("App has e to background!")
            }
            setAppState(nextAppState)
        }
       //appState updated here
       console.log(appState, "app state")
        return <View />
    }
Share Improve this question edited Jul 7, 2020 at 10:07 Mervzs asked Jul 7, 2020 at 10:03 MervzsMervzs 1,1548 silver badges21 bronze badges 14
  • You should fix this const [appState, setAppState] = useState < AppStateStatus > AppState.currentState – Joshua Commented Jul 7, 2020 at 10:07
  • How do you know the state isn't updating inside the callback? setAppState(nextAppState) is the last call within the function. – Drew Reese Commented Jul 7, 2020 at 10:08
  • its not the issue. changed it. i just forgot to include parantheses there. – Mervzs Commented Jul 7, 2020 at 10:08
  • 2 @DrewReese. It updates. but inside the listener the value is still same. – Mervzs Commented Jul 7, 2020 at 10:32
  • 1 @JaredSmith State updates are asynchronous. even though that is true, saying that will cause people to try await setState. Also the main problem with setting state created with useState in functional ponents is stale closures – HMR Commented Jul 7, 2020 at 10:42
 |  Show 9 more ments

2 Answers 2

Reset to default 7

In your code appState is a stale closure the linter should have told you that you have missing dependencies.

I think the following will work

const _handleAppStateChange = useCallback(
  (nextAppState) =>
    //use callback for state setter so you don't
    //  create needless dependency or (as you did)
    //  create a stale closure
    setAppState((currentAppState) => {
      //logs current appstate
      console.log(currentAppState, 'app state');
      if (
        currentAppState.match(/inactive|background/) &&
        nextAppState === 'active'
      ) {
        console.log('App has e to the foreground!');
        activateRealtime();
      } else if (
        currentAppState === 'active' &&
        nextAppState.match(/inactive|background/)
      ) {
        console.log('App has e to background!');
      }
      return nextAppState;
    }),
  //only pass function as _handleAppStateChange
  //  on mount by providing empty dependency
  []
);
useEffect(() => {
  AppState.addEventListener(
    'change',
    _handleAppStateChange
  );
  //clean up code when ponent unmounts
  return () =>
    AppState.removeEventListener(
      'change',
      _handleAppStateChange
    );
  //_handleAppStateChange is a dependency but useCallback
  //  has empty dependency so it is only created on mount
}, [_handleAppStateChange]);

Acceptable Solution

I was doing what the accepted answer suggested but it was still not working for me. I not only needed to call useCallback but I also needed to add my element's ref to the useEffect's dependency array as it was changing at some point and so the triggered callback was still using old data. I'm not sure how the callback was even being triggered then if element.current was replaced...

const elementRef = useRef(null);

useEffect(() => {
  elementRef.current.addEventListener("change", _handleAppStateChange)
  return () => elementRef.current.removeEventListener("change", _handleAppStateChange)
}, [elementRef.current])

...

return (
  <div ref={elementRef}>
  ...

Preferred Solution

However, as in my case, if you are rendering the element yourself, moving the event directly to the target element also fixes the issue. It not only makes the code simpler but it also just works whether you use useCallback or not!

const elementRef = useRef(null);

...

return (
  <div ref={elementRef} onChange={_handleAppStateChange}>
  ...

In this case, React will directly reattach the updated function to the element on every re-render.

本文标签: