简体   繁体   English

为什么我不能在 useEffect 回调中更新我的组件的 state?

[英]Why can't I update the state of my component from within a useEffect callback?

I am trying to create a timer that is started once the app is in the background, and then when the app is called to the foreground again the app checks if it has been 15 minutes (for ease of testing I am using 15s currently).我正在尝试创建一个计时器,该计时器在应用程序处于后台时启动,然后当应用程序再次被调用到前台时,应用程序会检查它是否已经过了 15 分钟(为了便于测试,我目前使用的是 15 秒)。 If it has the app logs the user out, else the timer resets.如果它让应用程序将用户注销,则计时器会重置。

If this is a dupe please refer me - I just couldn't find anything.如果这是一个骗局,请转介我 - 我找不到任何东西。 I've been trying to work this out for a couple of days now.这几天我一直在努力解决这个问题。

Here is my thinking:这是我的想法:

  • I listen in to the app state change using AppState.addEventListener in useEffect.我在 useEffect 中使用 AppState.addEventListener 监听应用程序 state 更改。 This fires a callback function on change which allows me to access the actual state that it has changed to.这会在更改时触发回调 function,这使我可以访问它已更改为的实际 state。
  • I store Date.now() of when the user puts the app into the background in a setState hook.我在 setState 挂钩中存储用户何时将应用程序放入后台的 Date.now() 。
  • When the app is called into the foreground, I then do Date.now() minus the stored state to work out if the time has elapsed.当应用程序被调用到前台时,我然后执行 Date.now() 减去存储的 state 以计算时间是否已过。

Here is my problem:这是我的问题:

  • When I attempt to reset the timer using setState from within the useEffect callback, it does nothing.当我尝试在 useEffect 回调中使用 setState 重置计时器时,它什么也不做。 I can't think of how else I'm supposed to be updating the state to do this.我想不出我应该如何更新 state 来做到这一点。 I have tried to also make these calls as references to other functions and it does the same thing我也尝试将这些调用作为对其他函数的引用,它做同样的事情

Things to note:注意事项:

  • This is being handled on my root app navigator one the user is authenticated.这是在我的根应用程序导航器上处理的,用户已通过身份验证。 Pretty sure this is irrelevant, but just in case anyone was wondering.很确定这无关紧要,但以防万一有人想知道。
  • I have tried doing this using redux and it's the same issue.我曾尝试使用 redux 执行此操作,这是同样的问题。 Dispatching state updates do not get executed.调度 state 更新不会被执行。
  • I am using an expo managed workflow, so if you have any alternative npm suggestions they need to be compatible.我正在使用 expo 托管工作流,因此如果您有任何替代 npm 建议,它们需要兼容。

const rootNavigator = () => {

  const dispatch = useDispatch()
  const [loginTimer, setLoginTimer] = useState(Date.now())

  useEffect(() => {
    AppState.addEventListener('change', handleAppStateChange)

    return (() => {
      AppState.removeEventListener('change', handleAppStateChange)
    })
  }, [])

  const handleAppStateChange = (state) => {

    if (state === "inactive" || state === "background") {
        setLoginTimer(Date.now())
      }
    if (state === "active") {
      if (Date.now() - loginTimer < 15000) {
        setLoginTimer(Date.now())
      } else if (Date.now() - loginTimer >= 15000) {
          dispatch(logout())
      }
    }
  }


  return (
    <SomeComponent />
    ...
  ) 

}

I am almost certain this is a pretty basic error I am running into because my understanding of how this is meant to be handled is not yet well formed enough.我几乎可以肯定这是我遇到的一个非常基本的错误,因为我对如何处理它的理解还不够完善。

Thanks in advance.提前致谢。

The problem is coming from your reference to useState data in the callback function handleAppStateChange() .问题来自您在回调 function handleAppStateChange()中对useState数据的引用。 The callback for AppState.addEventListener is registered when the component mounts, but a snapshot of the current state is used for that callback each time it is called, and therefore, it will not be reactive in the way you would like. AppState.addEventListener的回调在组件挂载时注册,但当前 state 的快照在每次调用时用于该回调,因此,它不会以您想要的方式响应。

The good news is that there are a number of ways to fix this issue, and some of those are explained in nice detail in this great answer here .好消息是有很多方法可以解决这个问题,其中一些在这个很好的答案中得到了很好的详细解释。

My solution, using information from Jacob K's link.我的解决方案,使用来自 Jacob K 链接的信息。 It was very simple in the end and I got it down to an even more succinct statement.最后它非常简单,我把它归结为一个更简洁的陈述。 Can probably use a ternary, but this works just fine.可能可以使用三元,但这很好用。


const rootNavigator = () => {

  const dispatch = useDispatch()
  const timer = useRef(Date.now())

  useEffect(() => {
    AppState.addEventListener('change', handleAppStateChange)

    return (() => {
      AppState.removeEventListener('change', handleAppStateChange)
    })
  }, [])

  const handleAppStateChange = (state) => {
    if (state === "inactive" || state === "background") {
      timer.current = Date.now()
    }
    if (state === "active" && (Date.now() - timer.current > 15000)) {
      dispatch(logout())
    }
  }


  return (
    <SomeComponent />
    ...
  ) 

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM