如何使用React钩子和使用UseInterval设置状态

发布于 2025-02-08 06:17:43 字数 2963 浏览 2 评论 0原文

我了解使用Interval使您可以将功能组件作为参数传递,因此我试图利用它,并设置具有功能组件中使用的值的设置状态。 在以下代码中,我希望ExecutionsPage获取ProjectScans和IsProjectScansFetchComplete,它们从usefetfethprojectprojectscansbyuser()挂接作为数组中返回:

1 const ExecutionsPage: React.FC = () => {
2     let user:string|null = sessionStorage.getItem('user')
3     const UPDATE_TABLE_TIMER: number = 30000; // Time (ms) until we call the API again to update the scans table
4     const [stateProjectScans, setStateProjectScans] = useState<Array<IProjectScan>>([]);
5     const [stateIsProjectScansFetchComplete, setStateIsProjectScansFetchComplete] = useState<boolean>(false);
6     function CallProjectScans(){
7         let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user)
8         return [projectScans, isProjectScansFetchComplete]
9     }
10    useEffect(() => {
11       let [projectScans, isProjectScansFetchComplete] = CallProjectScans()
12       // @ts-ignore
13        setStateProjectScans(projectScans)
14       // @ts-ignore
15        setStateIsProjectScansFetchComplete(isProjectScansFetchComplete)
16    }, [])
17    const GetProjectScans = () => {
18        let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user);
19        setStateProjectScans(projectScans)
20    };
21    // @ts-ignore
22    useInterval(GetProjectScans(), UPDATE_TABLE_TIMER)

在使用此代码时,我会遇到此错误:“错误:“重新订阅者太多。限制了防止无限循环的渲染数。” 因此,话虽如此,这是我的问题:

  1. 在可读性方面,我使用的功能太多了吗?
  2. 目的是每30秒钟在第7行上调用React Hook,并在第4和5行上更新州。是否有比我当前尝试的更好的方法了?
  3. 我尝试使用useseffect()来防止上述错误(通过将状态设置为executionspage的初始渲染,而不是重新读取它),但是我仍然可以得到它。为什么?

编辑:这是usefetchprojectscansbyuser()的函数定义:

export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
    const [projectScans, setProjectScans] = useState<IProjectScan[]>([]);
    //update here
    const [isProjectScansFetchComplete, setIsProjectScansFetchComplete] = useState<boolean>(false);
    const {enqueueSnackbar, } = useSnackbar();
    
    useEffect(() => {
        ...
        /*function callJSONFunction(json) {
                let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
                setProjectScans(projectScans);
        }*/
            const fetchProjectScans = async () => {
                try {
                    const response = await fetch(URL);
                    if (!response.ok) throw response.statusText;
            
                    const json = await response.json();
                    let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
                    setProjectScans(projectScans);
                ...
                finally {
                    setIsProjectScansFetchComplete(true);
                }
            };
            fetchProjectScans();
        }
    }, []);
    return [projectScans, isProjectScansFetchComplete];
}

I understand useInterval allows you to pass functional components as parameters, so I'm trying to take advantage of that and setting states with values used inside a functional component.
In the following code, I want the ExecutionsPage to fetch projectScans and isProjectScansFetchComplete, which are returned from the useFetchProjectScansByUser() hook as an array:

1 const ExecutionsPage: React.FC = () => {
2     let user:string|null = sessionStorage.getItem('user')
3     const UPDATE_TABLE_TIMER: number = 30000; // Time (ms) until we call the API again to update the scans table
4     const [stateProjectScans, setStateProjectScans] = useState<Array<IProjectScan>>([]);
5     const [stateIsProjectScansFetchComplete, setStateIsProjectScansFetchComplete] = useState<boolean>(false);
6     function CallProjectScans(){
7         let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user)
8         return [projectScans, isProjectScansFetchComplete]
9     }
10    useEffect(() => {
11       let [projectScans, isProjectScansFetchComplete] = CallProjectScans()
12       // @ts-ignore
13        setStateProjectScans(projectScans)
14       // @ts-ignore
15        setStateIsProjectScansFetchComplete(isProjectScansFetchComplete)
16    }, [])
17    const GetProjectScans = () => {
18        let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user);
19        setStateProjectScans(projectScans)
20    };
21    // @ts-ignore
22    useInterval(GetProjectScans(), UPDATE_TABLE_TIMER)

In using this code, I'm getting this error: "Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
So, with all that said, here are my questions:

  1. Readability-wise, am I using too many functions?
  2. The goal is to call the React hook on line 7 every 30 seconds and update the states on lines 4 and 5. Is there a better way to do this than how I'm currently attempting it?
  3. I tried using useEffect() to prevent the aforementioned error (by setting the states on the initial render of the ExecutionsPage, instead of rerendering it), but I am still getting it. Why?

Edit: Here's the function definition of useFetchProjectScansByUser():

export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
    const [projectScans, setProjectScans] = useState<IProjectScan[]>([]);
    //update here
    const [isProjectScansFetchComplete, setIsProjectScansFetchComplete] = useState<boolean>(false);
    const {enqueueSnackbar, } = useSnackbar();
    
    useEffect(() => {
        ...
        /*function callJSONFunction(json) {
                let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
                setProjectScans(projectScans);
        }*/
            const fetchProjectScans = async () => {
                try {
                    const response = await fetch(URL);
                    if (!response.ok) throw response.statusText;
            
                    const json = await response.json();
                    let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
                    setProjectScans(projectScans);
                ...
                finally {
                    setIsProjectScansFetchComplete(true);
                }
            };
            fetchProjectScans();
        }
    }, []);
    return [projectScans, isProjectScansFetchComplete];
}

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

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

发布评论

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

评论(2

后eg是否自 2025-02-15 06:17:43

正如预期的那样,您的钩子使用状态存储了对服务器的最后一个调用的结果。

如果要修改/更新该状态,则应通过添加新状态变量并将其传递到依赖项数组中,以usefect fired fire。然后,您可以将其转移回挂钩呼叫者以更新该依赖项数组的方式,因此实际上您可以从钩子外部控制更新。

这正是钩子应该如何工作的方式:将逻辑划分,并将简单的API暴露于钩子的消费者。

export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
    // make new state variable to control useEffect firing
    const [update, setUpdate] = useState(0)
    useEffect(() => {
        console.log("I will fire every time the update var changes")
        console.log("Instead of only on mount")
    }, [update]) // pass the state var to dep array
    // create a function that will update state reliably every call
    const updateFunc = () => setUpdate((v) => v+1)
    // pass it back to the hook consumer
    return [X, Y, updateFunc]
}

const MyComponent = () => {
     // now we have an update function to call from the consumer
     const [X, Y, update] = useFetchProjectScansByUser('user')

     useEffect(() => {
         // call the update function every 3 seconds, which will re-run the hook useEffect
         const id = setInterval(() => update(), 3000)
         return () => clearInterval(id)
     }, [])
     // if a hook state updates, this component which consumes that hook will re-render
     // thus X and Y will be updated.
     return <div>{X} {Y}</div>
}

As expected, your hook uses state to store the results of the last call to the server.

If you want to modify/update that state, you should make your sever call in the useEffect fire again by adding a new state variable and passing it to the dependency array. You can then pass back to the hook caller a way to update that dependency array, so in effect you can control the update from outside the hook.

This is exactly how hooks should work: compartmentalize logic, and expose a simple API to the consumers of the hook.

export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
    // make new state variable to control useEffect firing
    const [update, setUpdate] = useState(0)
    useEffect(() => {
        console.log("I will fire every time the update var changes")
        console.log("Instead of only on mount")
    }, [update]) // pass the state var to dep array
    // create a function that will update state reliably every call
    const updateFunc = () => setUpdate((v) => v+1)
    // pass it back to the hook consumer
    return [X, Y, updateFunc]
}

const MyComponent = () => {
     // now we have an update function to call from the consumer
     const [X, Y, update] = useFetchProjectScansByUser('user')

     useEffect(() => {
         // call the update function every 3 seconds, which will re-run the hook useEffect
         const id = setInterval(() => update(), 3000)
         return () => clearInterval(id)
     }, [])
     // if a hook state updates, this component which consumes that hook will re-render
     // thus X and Y will be updated.
     return <div>{X} {Y}</div>
}
冷心人i 2025-02-15 06:17:43

钩子的第一个规则之一是:仅在顶级呼叫挂钩

不要在循环,条件或嵌套功能中调用钩子。取而代之的是,在任何早期返回之前,请务必在React功能的最高级别上使用钩子。通过遵循此规则,您可以确保每次组件渲染时都以相同的顺序调用钩子。这就是允许反应正确保留多个usestate使用effeffect调用的钩子状态的原因。

因此,您需要更改代码,并将每个React挂钩移至顶部。如文档中所述,您甚至无法在另一个钩子之前返回。无论如何,要修复代码,您需要从功能内部删除React Hook UsefetchProtchScansByuser

One of the first rules of hooks is: Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

So, you'd need to change your code and move every React hook to the top. As stated in the docs you cannot even return earlier before another hook. Anyway, to fix the code then you need to remove the React hook useFetchProjectScansByUser from inside the functions.

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