英文:
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(() => {
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>);
}```
答案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(() => {
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);
});
}, []);
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论