I am new to reactJS and am writing code so that before the data is loaded from DB, it will show loading message, and then after it is loaded, render components with the loaded data. To do this, I am using both useState hook and useEffect hook. Here is the code:
问题是,当我检查console.log时,useEffect被触发了两次。因此,代码将两次查询相同的数据,这是应该避免的。
下面是我写的代码:
import React from 'react';
import './App.css';
import {useState,useEffect} from 'react';
import Postspreview from '../components/Postspreview'
const indexarray=[]; //The array to which the fetched data will be pushed
function Home() {
const [isLoading,setLoad]=useState(true);
useEffect(()=>{
/*
Query logic to query from DB and push to indexarray
*/
setLoad(false); // To indicate that the loading is complete
})
},[]);
if (isLoading===true){
console.log("Loading");
return <div>This is loading...</div>
}
else {
console.log("Loaded!"); //This is actually logged twice.
return (
<div>
<div className="posts_preview_columns">
{indexarray.map(indexarray=>
<Postspreview
username={indexarray.username}
idThumbnail={indexarray.profile_thumbnail}
nickname={indexarray.nickname}
postThumbnail={indexarray.photolink}
/>
)}
</div>
</div>
);
}
}
export default Home;
有人能帮助我理解为什么它被调用两次,以及如何正确地修复代码吗?
非常感谢!
将console.log放在useEffect中
可能有其他副作用导致组件重新渲染,但useEffect本身只会被调用一次。您可以通过下面的代码确定地看到这一点。
useEffect(()=>{
/*
Query logic
*/
console.log('i fire once');
},[]);
如果日志“i fire once”被触发不止一次,这意味着您的问题
三件事之一。
该组件在页面中出现多次
这一点应该很明显,您的组件在页面中出现了几次,每一次都将挂载并运行useEffect
树上更高的东西正在卸载和重新安装
组件被强制卸载并在初始渲染时重新安装。这可能是发生在树的更高位置的“关键”更改。你需要使用这个useEffect上升到每一层,直到它只呈现一次。然后你应该能找到原因或重新安装。
反应。开启严格模式
StrictMode将组件呈现两次(在开发中,而不是生产中),以便检测代码中的任何问题并警告您(这可能非常有用)。
这个答案是由@johnhendirx指出的,由@rangfu写的,如果这是你的问题,请给他一些爱。如果您因此而遇到问题,这通常意味着您没有按照预期的目的使用useEffect。在测试文档中有一些很好的信息,你可以在这里阅读
不知道为什么你不把结果放在状态,这里有一个例子,调用效果一次,所以你必须在代码中做了一些事情,没有发布,使它再次呈现:
const App = () => {
const [isLoading, setLoad] = React.useState(true)
const [data, setData] = React.useState([])
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
setLoad(false)//causes re render
setData(data)//causes re render
})
},[])
//first log in console, effect happens after render
console.log('rendering:', data.length, isLoading)
return <pre>{JSON.stringify(data, undefined, 2)}</pre>
}
//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
为了防止额外的渲染,你可以在一个状态下合并数据和加载:
const useIsMounted = () => {
const isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};
const App = () => {
const [result, setResult] = React.useState({
loading: true,
data: []
})
const isMounted = useIsMounted();
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
//before setting state in async function you should
// alsways check if the component is still mounted or
// react will spit out warnings
isMounted.current && setResult({ loading: false, data })
})
},[isMounted])
console.log(
'rendering:',
result.data.length,
result.loading
)
return (
<pre>{JSON.stringify(result.data, undefined, 2)}</pre>
)
}
//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
正如其他人已经指出的那样,这很可能是由于React 18.0引入了严格模式功能。
我写了一篇博客文章,解释了为什么会发生这种情况,以及你可以做些什么来解决它。
但如果你只是想看代码,这里:
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
// My actual effect logic...
...
}
}, [])
或作为可重复使用的钩子:
import type { DependencyList, EffectCallback } from "react"
import { useEffect } from "react"
export function useEffectUnsafe(effect: EffectCallback, deps: DependencyList) {
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
effect()
}
}, deps)
}
请记住,只有在不得已的情况下才应该使用这种解决方案!
下面是为您的目的定制的钩子。这对你的情况可能有帮助。
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
*
* @param effect
* @param dependencies
* @description Hook to prevent running the useEffect on the first render
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependancies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void) = () => {};
/**
* Updating the ref to false on the first render, causing
* subsequent render to execute the effect
*
*/
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
/**
* Preserving and allowing the Destructor returned by the effect
* to execute on component unmount and perform cleanup if
* required.
*
*/
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependancies);
}