简体   繁体   English

useEffect 挂钩内的 Graphql 订阅无法访问最新状态

[英]Graphql subscriptions inside a useEffect hook doesn't access latest state

I'm building a basic Slack clone.我正在构建一个基本的 Slack 克隆。 So I have a "Room", which has multiple "Channels".所以我有一个“房间”,它有多个“频道”。 A user subscribes to all messages in a Room, but we only add them to the current message list if the new message is part of the user's current Channel用户订阅了 Room 中的所有消息,但如果新消息是用户当前频道的一部分,我们只会将它们添加到当前消息列表中

const [currentChannel, setCurrentChannel] = useState(null);

const doSomething = (thing) => {
  console.log(thing, currentChannel)
}

useEffect(() => {
  // ... Here I have a call which will grab some data and set the currentChannel

  Service.joinRoom(roomId).subscribe({
    next: (x) => {
      doSomething(x)
    },
    error: (err: any) => { console.log("error: ", err) }
  })
}, [])

I'm only showing some of the code here to illustrate my issue.我只是在这里展示一些代码来说明我的问题。 The subscription gets created before currentChannel gets updated, which is fine, because we want to listen to everything, but then conditionally render based on currentChannel .订阅在currentChannel更新之前创建,这很好,因为我们想听一切,然后根据currentChannel有条件地呈现。

The issue I'm having, is that even though currentChannel gets set correctly, because it was null when the next: function was defined in the useEffect hook, doSomething will always log that currentChannel is null.我遇到的问题是,即使currentChannel设置正确,因为在useEffect钩子中定义next:函数时它为空, doSomething将始终记录currentChannel为空。 I know it's getting set correctly because I'm displaying it on my screen in the render.我知道它设置正确,因为我在渲染的屏幕上显示它。 So why does doSomething get scoped in a way that currentChannel is null?那么为什么doSomethingcurrentChannel为空的方式获得范围? How can I get it to call a new function each time that accesses the freshest state of currentChannel each time the next function is called?我怎样才能得到每个访问的最新鲜的状态时它来调用一个新的函数currentChannel每个时间next函数调用? I tried it with both useState , as well as storing/retrieving it from redux, nothing is working.我尝试使用useState以及从 redux 存储/检索它,但没有任何效果。

Actually it is related to all async actions involving javascript closures: your subscribe refers to initial doSomething (it's recreated on each render) that refers to initial currentChannel value.实际上,它与涉及 javascript 闭包的所有异步操作有关:您的subscribe指的是初始doSomething (它在每次渲染时重新创建),它指的是初始currentChannel值。 Article with good examples for reference: https://dmitripavlutin.com/react-hooks-stale-closures/文章有很好的例子供参考: https : //dmitripavlutin.com/react-hooks-stale-closures/

What can we do?我们可以做什么? I see at least 2 moves here: quick-n-dirty and fundamental.我看到这里至少有两个动作:快速和基本。

  1. We can utilize that useState returns exact the same(referentially same) setter function each time and it allows us to use functional version:我们可以利用useState返回完全相同(引用相同)的 setter 函数,它允许我们使用函数版本:
const doSomething = (thing) => {
  setCurrentChannel(currentChannelFromFunctionalSetter => {
    console.log(thing, currentChannelFromFunctionalSetter);
    return currentChannelFromFunctionalSetter;
  }
}
  1. Fundamental approach is to utilize useRef and put most recent doSomething there:基本方法是利用useRef并将最新的doSomething放在那里:
const latestDoSomething = useRef(null);
...
const doSomething = (thing) => { // nothing changed here
  console.log(thing, currentChannel)
}
latestDoSomething.current = doSomething; // happens on each render

useEffect(() => {
  Service.joinRoom(roomId).subscribe({
    next: (x) => {
      // we are using latest version with closure on most recent data
      latestDoSomething.current(x) 
    },

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

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