RTK 查询挂钩 - 防止在 arg 更改时轮询自动重新获取

发布于 2025-01-11 23:06:15 字数 461 浏览 0 评论 0原文

我试图刷新查询挂钩中的令牌,每 9 秒使用轮询功能:

"/App.tsx"
..
...
const [storedToken, setStoredToken] = useState(getStoredToken());

const { data, error, refetch } = useRefreshUserQuery(storedToken, {
   pollingInterval: 9000,
   // refetchOnMountOrArgChange: false // -> This has no effect
});
...
..

问题是,当使用 setStoredToken(token) 设置令牌时,它会立即重新获取。新令牌作为参数传递给查询钩子 storedToken 并立即重新获取(就像无限循环)。

能够做到这一点就太好了。有没有更好的方法通过轮询刷新令牌?

I'm trying to refresh a token in a Query hook, with the polling feature every 9 seconds:

"/App.tsx"
..
...
const [storedToken, setStoredToken] = useState(getStoredToken());

const { data, error, refetch } = useRefreshUserQuery(storedToken, {
   pollingInterval: 9000,
   // refetchOnMountOrArgChange: false // -> This has no effect
});
...
..

The problem is, it re-fetches instantly when the token is set with setStoredToken(token). The new token is passed as argument to the Query hook storedToken and refetch immediately (like an infinite loop).

That would be pretty neat to be able to do this. Is there any better way to refresh a token with polling?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烛影斜 2025-01-18 23:06:15

我相信这个问题在 RTK-Q 级别上没有什么需要解决的 - 这是钩子和渲染生命周期架构的一个非常常见和预期的“限制”。我觉得 RTK-Q 轮询不符合您的要求,当然,您正在努力实现 - 它实际上不是常识中的轮询。至少 - 它是条件轮询,需要更多的逻辑)

所以我会通过 debouncing 和 useEffect 来解决这个问题:

  const [storedToken, setStoredToken] = useState<string>(getStoredToken());
  const [tokenDebounced] = useDebounce(storedToken, 9000);
  const { data } = useRefreshUserQuery(tokenDebounced);

  useEffect(() => {
    if (data) {
      setStoredToken(data);
      // console.log(newToken);
    }
  }, [data]);

useEffect 内容和 data 内容可能不同,但总体而言想法应该很清楚。

useDebounce 来自 https://www.npmjs.com/package/use-debounce ,
但如果您已经定义了一些实现,那么您自己的实现应该工作相同。

另一个想法,稍微触动你的 AUTH 设置 - 就是

const [storedToken, setStoredToken] = useState<string>(getStoredToken());

完全避免这部分,并保持 useRefreshUserQuery() 不带参数。

最有可能和常见的是将令牌存储在 localStorage 或 redux\other 存储中,并基于将设置标头的 fetchBaseQuery 定义新的 baseQuery和/或包含带有 credentials: "include" 的 cookie,以及来自 localStorage 或 redux\other 存储的令牌。当然,您需要在第一次 AUTH 期间存储它。

我认为 RTK-Q auth 示例也以某种方式揭示了这种情况:

https ://redux-toolkit.js.org/rtk-query/usage/examples#authentication

在你避免使用 useState 和查询钩子参数之后 - 你将能够使用轮询没有问题:

const { data, error, refetch } = useRefreshUserQuery(undefined ,{
   pollingInterval: 9000,
});

I believe that issue is nothing to solve on RTK-Q level - it's a pretty common and expected "limitation" of hooks and rendering lifecycle architecture. And I feel that RTK-Q polling just won't fit your requirements here, of course, that you are trying to achieve - it's not actually polling in common sense. At least - it's conditional polling, which needs some more logic)

So I would solve this just by debouncing and useEffect:

  const [storedToken, setStoredToken] = useState<string>(getStoredToken());
  const [tokenDebounced] = useDebounce(storedToken, 9000);
  const { data } = useRefreshUserQuery(tokenDebounced);

  useEffect(() => {
    if (data) {
      setStoredToken(data);
      // console.log(newToken);
    }
  }, [data]);

The useEffect content and data content may differ, but the overall idea should be clear.

useDebounce is from https://www.npmjs.com/package/use-debounce,
but your own implementations should work the same if you have some defined already.

Another idea, touching you AUTH setup a bit - is just avoid

const [storedToken, setStoredToken] = useState<string>(getStoredToken());

the part at all, and keep useRefreshUserQuery() without params.

Most likely and common is to store the token in localStorage or redux\other store, and define new baseQuery, based on fetchBaseQuery that will set header and\or to include cookies with credentials: "include" with a token from localStorage or redux\other store. Definitely, you will need to store it during the first AUTH then.

I think RTK-Q auth example reveals this case in some way also:

https://redux-toolkit.js.org/rtk-query/usage/examples#authentication

After you'll avoid that useState and query hook param - you'll be able to use polling with no issues:

const { data, error, refetch } = useRefreshUserQuery(undefined ,{
   pollingInterval: 9000,
});
梓梦 2025-01-18 23:06:15

这里的“轮询”意味着“在获得数据后 X 秒获取”,但是当然您必须获取第一个数据本身 - 这就是第一次获取。如果您阻止这种情况,轮询也将永远不会开始。

老实说,这是一个奇怪的要求,这样做会用数十个状态条目填充您的缓存。

我会做一些不同的事情 - 在 端点生命周期

这是未经测试的伪代码,您需要对其进行一些调整:

function waitFor(ms) {
  return new Promise(resolve => setTimeout(() => resolve("waited"), ms))
}


currentToken: build.query({
  query() {
    // whatever you need for the first token here
  },
async onCacheEntryAdded(
        arg,
        { updateCachedData, cacheDataLoaded, cacheEntryRemoved }
      ) {
        try {
          // wait for the initial query to resolve before proceeding
          await cacheDataLoaded

          while (true) {
            const result = await Promise.race(waitFor(9000), cacheEntryRemoved)
            if (result !== "waited") {
              // cache entry was removed, stop the loop
              break
            }
            // make a fetch call to get a new token here
            const newToken = fetch(...)

            updateCachedData((oldData) => newToken)
          }
      },
})

然后

const result = useCurrentTokenQuery()

在您的组件中

"Polling" here means "fetch X seconds after I have data", but of course you have to get the first data itself - and that is that first fetch. If you prevent that, polling will also never start.

Tbh., this is kind of a weird requirement and doing it like this will fill your cache with dozens of state entries.

I'd do something a little differently - solve it in the endpoint lifecycle.

This is untested pseudocode and you'll need to adjust it a bit:

function waitFor(ms) {
  return new Promise(resolve => setTimeout(() => resolve("waited"), ms))
}


currentToken: build.query({
  query() {
    // whatever you need for the first token here
  },
async onCacheEntryAdded(
        arg,
        { updateCachedData, cacheDataLoaded, cacheEntryRemoved }
      ) {
        try {
          // wait for the initial query to resolve before proceeding
          await cacheDataLoaded

          while (true) {
            const result = await Promise.race(waitFor(9000), cacheEntryRemoved)
            if (result !== "waited") {
              // cache entry was removed, stop the loop
              break
            }
            // make a fetch call to get a new token here
            const newToken = fetch(...)

            updateCachedData((oldData) => newToken)
          }
      },
})

and then just

const result = useCurrentTokenQuery()

in your component

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文