简体   繁体   English

为什么我的 React 组件渲染了两次?

[英]Why is my React component is rendering twice?

I don't know why my React component is rendering twice.我不知道为什么我的 React 组件会渲染两次。 So I am pulling a phone number from params and saving it to state so I can search through Firestore.所以我从参数中提取一个电话号码并将其保存到状态,以便我可以搜索 Firestore。 Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points.一切似乎都工作正常,除了渲染两次......第一个渲染电话号码和零点。 The second time it renders all the data is displayed correctly.第二次渲染时所有数据都正确显示。 Can someone guide me to the solution.有人可以指导我找到解决方案。

class Update extends Component {
constructor(props) {
    super(props);
    const { match } = this.props;
    this.state = {
        phoneNumber: match.params.phoneNumber,
        points: 0,
        error: ''
    }
}

getPoints = () => {
    firebase.auth().onAuthStateChanged((user) => {
        if(user) {
            const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
            docRef.get().then((doc) => {
                if (doc.exists) {
                const points = doc.data().points;
                this.setState(() => ({ points }));
                console.log(points);
                } else {
                // doc.data() will be undefined in this case
                console.log("No such document!");
                const error = 'This phone number is not registered yet...'
                this.setState(() => ({ error }));
                }
                }).catch(function(error) {
                console.log("Error getting document:", error);
                });
        } else {
            history.push('/')
        }
    });
}

componentDidMount() {
    if(this.state.phoneNumber) {
        this.getPoints();
    } else {
        return null;
    }
}

render() {
    return (
        <div>
            <div>
                <p>{this.state.phoneNumber} has {this.state.points} points...</p>
                <p>Would you like to redeem or add points?</p>
            </div>
            <div>
                <button>Redeem Points</button>
                <button>Add Points</button>
            </div>
        </div>
    );
  }
}

export default Update;

You are running your app in strict mode.您正在严格模式下运行您的应用程序。 Go to index.js and comment strict mode tag.转到 index.js 并注释严格模式标签。 You will find a single render.你会发现一个单一的渲染。

This happens is an intentional feature of the React.StrictMode.发生这种情况是 React.StrictMode 的有意功能。 It only happens in development mode and should help to find accidental side effects in the render phase.它只发生在开发模式下,应该有助于在渲染阶段发现意外的副作用。

From the docs:从文档:

Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic.严格模式不能自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。 This is done by intentionally double-invoking the following functions:...这是通过有意双重调用以下函数来完成的:...

^ In this case the render function. ^ 在这种情况下, render功能。

Official documentation of what might cause re-rendering when using React.StrictMode:使用 React.StrictMode 时可能导致重新渲染的官方文档:

https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

React is rendering the component before getPoints finishing the asynchronous operation. React 在getPoints完成异步操作之前渲染组件。

So the first render shows the initial state for points which is 0 , then componentDidMount is called and triggers the async operation.因此,第一个render显示points的初始状态为0 ,然后调用componentDidMount并触发异步操作。
When the async operation is done and the state been updated, another render is triggered with the new data.当异步操作完成并更新状态时,将使用新数据触发另一个render

If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering .如果需要,您可以显示加载器或指示数据正在被获取并且尚未准备好使用条件渲染显示

Just add another Boolean key like isFetching , set it to true when you call the server and set it to false when the data is received.只需添加另一个布尔键,例如isFetching ,在调用服务器时将其设置为 true,在接收到数据时将其设置为 false。

Your render can look something like this:你的渲染看起来像这样:

  render() {
    const { isFetching } = this.state;
    return (
      <div>
        {isFetching ? (
          <div>Loading...</div>
        ) : (
          <div>
            <p>
              {this.state.phoneNumber} has {this.state.points} points...
            </p>
            <p>Would you like to redeem or add points?</p>
            <div>
              <button>Redeem Points</button>
              <button>Add Points</button>
            </div>
          </div>
        )}
      </div>
    );
  }

This is because of React Strict Mode code.这是因为 React Strict Mode 代码。

Remove -> React.StrictMode, from ReactDOM.render code.从 ReactDOM.render 代码中移除 -> React.StrictMode。

Will render 2 times on every re-render:每次重新渲染都会渲染 2 次:

ReactDOM.render(
  <React.StrictMode>
<App />
  </React.StrictMode>,
  document.getElementById('root')
);

Will render 1 time:将渲染 1 次:

ReactDOM.render(
  <>
<App />
  </>,
  document.getElementById('root')
);

PS no offence to people that are saying DONT worry about 'how many times it re-renders' ... if you are running a perpetual API fetch code every 1 second through a setTimeout and every time you use the API it costs you 0.01 cent, every single time it re-renders it fires the setTimeout function 2 times (which means you are doubling the calls every second), which means after 5 seconds you are running 1000+ setTimeouts each calling API at the same time and after 1 hour, this number will become a trillion+, so costs will become astranomical if you get it wrong. PS 对那些说不要担心“它重新渲染多少次”的人没有冒犯……如果您通过 setTimeout 每 1 秒运行一次永久 API 获取代码,并且每次使用该 API 都会花费您 0.01 美分,每次重新渲染时,它都会触发 setTimeout 函数 2 次(这意味着您每秒将调用加倍),这意味着在 5 秒后,您将在同一时间和 1 小时后运行 1000+ setTimeouts 每个调用 API,这个数字将超过一万亿,所以如果你弄错了,成本将变成天文数字。 This issue is fixed by removing React.StrictMode, so code will WAI.此问题已通过删除 React.StrictMode 得到解决,因此代码将 WAI。

React.StrictMode, makes it render twice, so that we do not put side effects in following locations React.StrictMode,让它渲染两次,这样我们就不会在以下位置放置副作用

constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)

All these methods are called more than once, so it is important to avoid having side-effects in them.所有这些方法都被多次调用,因此避免它们产生副作用很重要。 If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.如果我们忽略这个原则,很可能会导致不一致的状态问题和内存泄漏。

React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions. React.StrictMode 不能立即发现副作用,但它可以通过有意调用两次关键函数来帮助我们找到它们。

These functions are:这些功能是:

Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer

This behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.这种行为肯定会对性能产生一些影响,但我们不必担心,因为它只发生在开发中而不是生产中。
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/信用: https ://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/

it is done intentionally by react to avoid this remove它是通过反应故意完成的,以避免这个删除

  <React.StrictMode>     </React.StrictMode>

from index.js来自 index.js

React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. React 在内部使用它的虚拟 dom 和它的 diffing 算法监控和管理它的渲染周期,所以你不必担心重新渲染的数量。 Let the re-renders to be managed by react.让重新渲染由 React 管理。 Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it.即使调用了渲染函数,如果其中没有道具或状态更改,也有不会在 ui 上刷新的子组件。 Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.每个 setstate 函数调用都会通知 react 检查差异算法,并调用渲染函数。

So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.因此,在您的情况下,由于您在 getPoints 函数中定义了一个 setstate,它告诉 react 通过 render 函数重新运行差异过程。

I worked around this by providing a custom hook.我通过提供一个自定义钩子来解决这个问题。 Put the hook below into your code, then:将下面的钩子放入您的代码中,然后:

// instead of this:
useEffect( ()=> {
    console.log('my effect is running');
    return () => console.log('my effect is destroying');
}, []);

// do this:
useEffectOnce( ()=> {
    console.log('my effect is running');
    return () => console.log('my effect is destroying');
});

Here is the code for the hook:这是钩子的代码:

export const useEffectOnce = ( effect => {

    const destroyFunc = useRef();
    const calledOnce = useRef(false);
    const renderAfterCalled = useRef(false);

    if (calledOnce.current) {
        renderAfterCalled.current = true;
    }

    useEffect( () => {
        if (calledOnce.current) { 
            return; 
        }

        calledOnce.current = true;
        destroyFunc.current = effect();

        return ()=> {
            if (!renderAfterCalled.current) {
                return;
            }

            if (destroyFunc.current) {
                destroyFunc.current();
            }
        };
    }, []);
};

See this blog for the explanation.有关说明,请参阅此博客

Well, I have created a workaround hook for this.好吧,我为此创建了一个解决方法挂钩。 Check this, if it helps:检查这个,如果它有帮助:

import { useEffect } from "react";

const useDevEffect = (cb, deps) => {
  let ran = false;
  useEffect(() => {
    if (ran) return;
    cb();
    return () => (ran = true);
  }, deps);
};

const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";

export const useOnceEffect = isDev ? useDevEffect : useEffect;

CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix CodeSandbox 演示: https ://github.com/akulsr0/react-18-useeffect-twice-fix

Update based on React 18:基于 React 18 的更新:

import { useEffect, useRef } from "react";

export default function Component() {
    const isRunned = useRef(false);

    useEffect(() => {
        !isRunned.current &&
            {
                /* CODE THAT SHOULD RUN ONCE */
            };

        return () => {
            isRunned.current = true;
        };
    }, []);

    return <div> content </div>;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么我的 nextjs 组件渲染了两次? - Why my nextjs component is rendering twice? 使用 firebase 注册新用户时反应“在渲染不同组件时无法更新组件(`BrowserRouter`)”警告 - React "Cannot update a component (`BrowserRouter`) while rendering a different component" warning when registering a new user using firebase 为什么我的 React Native firebase 登录不起作用? - Why is my react native firebase login not working? 为什么在我的 React 应用程序上实施身份验证不起作用 - Why is implementing authentication on my react app not working 在 React 中渲染一个 firestore 时间戳 - Rendering a firestore timestamp in react 为什么我的 Python 应用程序总是在 AWS lambda 上冷启动两次? - Why does my Python app always cold start twice on AWS lambda? 我的组件仅在我对 IDE 进行编辑时呈现(React、Firebase 和 useEffect) - My component only renders when I make an edit to my IDE (React , Firebase & useEffect) 为什么我的组件在调用时无法运行? - Why is my component failing to run when I call it? 当我将表单提交到 firestore 时如何使我的反应组件重新呈现 - How to make my react component rerender when I submit a form into firestore 警告:在渲染不同的组件(`FormSocialIcon`)时无法更新组件(`BrowserRouter`) - Warning: Cannot update a component (`BrowserRouter`) while rendering a different component (`FormSocialIcon`)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM