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;

有人能帮助我理解为什么它被调用两次,以及如何正确地修复代码吗? 非常感谢!


当前回答

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

其他回答

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

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

reactStrictMode: false,

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

CodeSandbox_sample

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

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

不知道为什么你不把结果放在状态,这里有一个例子,调用效果一次,所以你必须在代码中做了一些事情,没有发布,使它再次呈现:

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>

好吧,现在评论这个可能有点晚了-但我发现了一个相当有用的解决方案,即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开发人员的想法-这指出了我实际上有更多的清理工作要做(像从存储中删除我的令牌等),在一切正常之前,但现在我可以导航到和从我的注销页面没有任何奇怪的事情发生。

我的意见…