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>


我使用这个作为我的替代useFocusEffect。我使用嵌套的react导航堆栈,如选项卡和抽屉,使用useEffect重构并不像预期的那样对我有效。

import React, { useEffect, useState } from 'react'
import { useFocusEffect } from '@react-navigation/native'

const app = () = {

  const [isloaded, setLoaded] = useState(false)


  useFocusEffect(() => {
      if (!isloaded) {
        console.log('This should called once')

        setLoaded(true)
      }
    return () => {}
  }, [])

}

还有一个例子,你在屏幕上导航了两次。


您很可能是在启用严格模式的开发环境中检查问题。 要验证这种情况,请搜索<React。StrictMode>标签并删除它,或构建用于生产。双重渲染的问题应该消失了。 来自React官方文档

严格模式不能自动为你检测副作用,但它可以通过使它们更具确定性来帮助你发现它们。这是通过有意地双重调用以下函数来实现的: 传递给useState、useMemo或useReducer的函数 […]

严格模式- Reactjs文档

类似的问题在这里,我的React组件渲染两次,因为严格模式


我遇到过这样的问题:

const [onChainNFTs, setOnChainNFTs] = useState([]);

将触发useEffect两次:

useEffect(() => {
    console.log('do something as initial state of onChainNFTs changed'); // triggered 2 times
}, [onChainNFTs]);

我确认组件MOUNTED ONLY ONCE和setOnChainNFTs没有被调用不止一次-所以这不是问题所在。

我通过将onChainNFTs的初始状态转换为null并进行空检查来修复它。

e.g.

const [onChainNFTs, setOnChainNFTs] = useState(null);
useEffect(() => {
if (onChainNFTs !== null) {
    console.log('do something as initial state of onChainNFTs changed'); // triggered 1 time!
}
}, [onChainNFTs]);

下面是为您的目的定制的钩子。这对你的情况可能有帮助。

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);
}


请检查你的index.js

  <React.StrictMode>
    <App />
  </React.StrictMode>

删除<React。StrictMode >包装 您现在应该发射一次

root.render(
    <App />
);

删除<反应。从index.js的StrictMode> 这段代码将是

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

this

root.render(
    <App />
);

React StrictMode在开发服务器上渲染组件两次


这是我们使用React.StrictMode时ReactJS的特性。StrictMode为它的后代节点激活额外的检查和警告。因为应用程序不应该崩溃的情况下,任何不良的做法在代码。我们可以说StrictMode是一个安全检查,用于两次验证组件以检测错误。

你会得到这个<React。组件根的StricyMode>。

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

如果你想限制组件渲染两次,你可以删除<React。StrictMode>并检查它。但是在糟糕的代码实践中,使用StrictMode来检测运行时错误是必要的。


没什么好担心的。当你在开发模式下运行React时。它有时会运行两次。在刺激环境中测试它,您的useEffect将只运行一次。别担心! !


如果你正在使用Next js,将reactStrictMode从“true”更改为false:

将此添加到你的next.config.js中

reactStrictMode: false,

我在React 18中找到了一个非常好的解释。在React中调用UseEffect两次

注意:在生产中,它可以正常工作。在开发环境中的严格模式下,有意增加两次安装,以处理错误和所需的清理。


对我来说是严格模式。删除索引处的严格模式组件。TSX或index.jsx


index.js > remove < react .jsStrictMode >包装


我使用CodeSandbox和删除防止了这个问题。

CodeSandbox_sample


新的React文档(目前处于测试阶段)有一个章节精确地描述了这种行为:

如何处理效果发射两次在开发

从文档中可以看出:

通常,答案是实现清理功能。清除函数应该停止或撤消Effect正在做的任何事情。经验法则是,用户不应该能够区分Effect运行一次(如在生产中)和设置→清理→设置序列(如您在开发中看到的)。

所以这个警告应该让你仔细检查你的useEffect,通常意味着你需要实现一个清理函数。


如果有人使用NextJS 13来这里,为了删除严格模式,你需要在next.config.js文件中添加以下内容:

const nextConfig = {
  reactStrictMode: false
}
module.exports = nextConfig

当我创建项目时,它默认使用“严格模式”,这就是为什么我必须显式设置它。


这可能不是理想的解决方案。但我用了一个变通办法。

var ranonce = false;
useEffect(() => {
    if (!ranonce) {

        //Run you code

        ranonce = true
    }
}, [])

尽管useEffect运行两次,但重要的代码只运行一次。


正如其他人已经指出的那样,这很可能是由于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)
}

请记住,只有在不得已的情况下才应该使用这种解决方案!


好吧,现在评论这个可能有点晚了-但我发现了一个相当有用的解决方案,即100%反应。

在我的情况下,我有一个令牌,我正在使用使一个POST请求注销我的当前用户。

我正在使用状态如下的减速机:

export const INITIAL_STATE = { 令牌:零 } export const logoutReducer = (state, action) => { Switch (action.type) { case ACTION_SET_TOKEN: 状态= { 状态, [action.name]: action.value }; 返回状态; 默认值: 抛出新的错误('无效的操作:${action} '); } } export const ACTION_SET_TOKEN = 0x1;

然后在我的组件中,我像这样检查状态:

import {useEffect, useReducer} from 'react'; import {INITIAL_STATE, ACTION_SET_TOKEN, logoutReducer} from "../reducers/logoutReducer"; const Logout = () => { const router = useRouter(); const [state, dispatch] = useReducer(logoutReducer, INITIAL_STATE); useEffect(() => { if (!state.token) { let token = 'x' // .... get your token here, i'm using some event to get my token dispatch( { type : ACTION_SET_TOKEN, name : 'token', value : token } ); } else { // make your POST request here } }

这个设计实际上很好——你有机会在POST请求之后从存储中丢弃你的令牌,确保POST在任何事情之前成功。对于异步的东西,你可以使用这样的形式:

POST()然后(异步。 () => {}). 抓住(异步 () => {}). 终于(异步)= > {})

所有运行在useEffect -工作100%,在我认为REACT开发人员的想法-这指出了我实际上有更多的清理工作要做(像从存储中删除我的令牌等),在一切正常之前,但现在我可以导航到和从我的注销页面没有任何奇怪的事情发生。

我的意见…