React useEffect,加载数据并更改该数据。

huangapple go评论61阅读模式
英文:

React useEffect, loading data and changing that data

问题

以下是您要翻译的内容:

"I have a simple component that I am trying to load posts in useEffect. The posts load fine, but when I then try to change the array in the setTimeout, and try to access the posts array, it is empty and therefore gives me an error when trying to change the title.

What am I doing wrong here? I assume that it is rerendering somewhere and setting the state back to the initial [], how can I fix this code so that it successfully changes the item in the array?

import { useEffect, useState } from "react";

export default function Posts() {

    const [posts, setPosts] = useState([]);
    console.log(posts.length);
    useEffect(() => {

        fetch('https://jsonplaceholder.typicode.com/posts')
            .then((response) => response.json())
            .then((json) => { setPosts(json); });

    }, []);

    setTimeout(() => {
        console.log(posts.length);
        var allPosts = [...posts];
        allPosts[10].title = "a new title";
        setPosts(allPosts);

    }, 6000);

    return (
        <div>
            {posts.map((post) => <div key={post.id}>{post.id}: <input type='text' readOnly value={post.title} /></div>)}
        </div>);
}```"

<details>
<summary>英文:</summary>

I have a simple component that I am trying to load posts in useEffect. The posts load fine, but when I then try to change the array in the setTimeout, and try to access the posts array, it is empty and therefore gives me an error when trying to change the title. 

What am I doing wrong here? I assume that it is rerendering somewhere and setting the state back to the initial [], how can I fix this code so that it successfully changes the item in the array? 

import { useEffect, useState } from "react";

export default function Posts() {

const [posts, setPosts] = useState([]);
console.log(posts.length);
useEffect(() =&gt; {

    fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;)
        .then((response) =&gt; response.json())
        .then((json) =&gt; { setPosts(json); });

}, []);

setTimeout(() =&gt; {
    console.log(posts.length);
    var allPosts = [...posts];
    allPosts[10].title = &quot;a new title&quot;;
    setPosts(allPosts);

}, 6000);

return (
    &lt;div&gt;
        {posts.map((post) =&gt; &lt;div key={post.id}&gt;{post.id}: &lt;input type=&#39;text&#39; readOnly value={post.title} /&gt;&lt;/div&gt;)}
    &lt;/div&gt;);

}```

答案1

得分: 2

以下是您要翻译的内容:

在这里最简单的做法是在fetch()完成后调用setTimeout,同时使用useState回调,以确保在超时执行时获取到最新的状态。

useEffect(() => {

    fetch('https://jsonplaceholder.typicode.com/posts')
        .then((response) => response.json())
        .then((json) => {
            setPosts(json);

            setTimeout(() => {
                setPosts((posts) => {
                    console.log(posts.length);
                    var allPosts = [...posts];
                    allPosts[10].title = "a new title";
                    return allPosts;
                })
            }, 6000);
        });

}, []);

您对React的渲染和状态工作方式感到困惑。这种确切类型的问题每天都会在Stackoverflow上被问几十次,所以我写了一个更长的答案,描述了闭包、渲染和React之间的互动。

在您的代码中,您的组件首次以空白的posts渲染,并启动一个setTimeout,该setTimeout“封闭”了posts的初始值。您的setTimeout回调会快照父范围中当前变量的值,这就是它所能访问的全部内容。

当您的fetch完成时,它调用setState并触发一个新的渲染,这是您组件函数的全新运行。您的setTimeout函数仍然存储在内存中,具有其先前快照的状态。新的setTimeout将访问新的渲染变量,但您的旧的setTimeout将首先触发,然后因为它只能访问初始渲染的数据而出错。

还要注意,在这个解决方案中仍然存在一个错误,因为如果组件在6秒内卸载,setTimeout回调将触发并尝试在卸载的组件上设置状态,这将引发错误。您需要使用ref来跟踪超时ID,并在卸载时将其清除。

英文:

The easiest thing to do here is to call the setTimeout after your fetch() finishes, along with the useState callback, to make sure you're grabbing the latest state when your timeout executes.


useEffect(() =&gt; {

    fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;)
        .then((response) =&gt; response.json())
        .then((json) =&gt; {
            setPosts(json);

            setTimeout(() =&gt; {
                setPosts((posts) =&gt; {
                    console.log(posts.length);
                    var allPosts = [...posts];
                    allPosts[10].title = &quot;a new title&quot;;
                    return allPosts;
                })
            }, 6000);
        });

}, []);

You're confused about how React renders and state work. This exact type of question is asked a few dozen times a day on Stackoverflow, so I wrote a longer answer describing the interaction of closures and rendering and React.

In your code, your component renders once with empty posts, and you kick off a setTimeout, which "closes over" the initial value of posts. Your setTimeout callback takes a snapshot of the current variables in the parent scope, and that's all it has access to.

When your fetch finishes, it calls setState and triggers a new render, which is an entirely new run of your component's function. Your setTimeout function is still hanging out in memory with its previous snapshot of the state. The new setTimeout will get access to the newer variables from the render, but your older one will fire first, and barf, because it only has access to the initial render's data.

Also note that in this solution, there's still a bug, because if your component unmounts before the 6 seconds is up, the setTimeout callback will fire and try to set state on a unmounted component, which will cause an error. It's up to you to track the timeout ID in a ref, and clear it on unmount.

huangapple
  • 本文由 发表于 2023年6月5日 05:55:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76402555.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定