Child component rendering multiple times [duplicate] - javascript

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through 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. You will find a single render.
This happens is an intentional feature of the 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.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects

This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);

React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
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.
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>
);
}

React.StrictMode, makes it render twice, so that we do not put side effects in following locations
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.
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/

it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js

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

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. Let the re-renders to be managed by 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. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
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.

Related

React document.addEventlistener keypress or keydown [duplicate]

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through 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. You will find a single render.
This happens is an intentional feature of the 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.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
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.
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>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
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.
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/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
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
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. Let the re-renders to be managed by 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. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
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.

useEffect execution order between sibling components

In this code:
import React from 'react';
import './style.css';
let Child2 = () => {
React.useEffect(() => {
console.log('Child 2');
}, []);
return <div />;
};
let Child1 = ({ children }) => {
return <div>{children}</div>;
};
let FirstComponent = () => {
React.useEffect(() => {
console.log('FirstComponent');
}, []);
return <div />;
};
export default function App() {
React.useEffect(() => {
console.log('Main App');
}, []);
return (
<div>
<FirstComponent />
<Child1>
<Child2 />
</Child1>
</div>
);
}
The output is:
FirstComponent
Child 2
Main App
Question
Is there some reliable source (e.g. docs) so that we can say that always useEffect of
FirstComponent will precede useEffect of Child2?
Why is this relevant?
If we are sure that effect from FirstComponent always runs first, then it could be useful to perform some initialization work there (maybe perform some side effect), which we want to be available to all other useEffects in the app. We can't do this with normal parent/child effects, because you can see that parent effect ("Main App") runs after child effect ("Child 2").
Answering the question asked: As far as I'm aware, React doesn't guarantee the order of the calls to your component functions for the children, though it would be really surprising if they weren't in first-to-last order between siblings (so, reliably calling FirstComponent at least once before calling Child1 the first time, in that App). But although the calls to createElement will reliably be in that order, the calls to the component functions are done by React when and how it sees fit. It's hard to prove a negative, but I don't think it's documented that they'll be in any particular order.
But:
If we are sure that effect from FirstComponent always runs first, then it could be useful to perform some initialization work there, which we want to be available to all other useEffects in the app. We can't do this with normal parent/child effects, because you can see that parent effect ("Main App") runs after child effect ("Child 2".)
I wouldn't do that even if you find documentation saying the order is guaranteed. Crosstalk between sibling components is not a good idea. It means the components can't be used separately from each other, makes the components harder to test, and is unusual, making it surprising to people maintaining the codebase. You can do it anyway, of course, but as is often the case, lifting state up most likely applies ("state" in the general case, not just component state). Instead, do any one-time initialization you need to do in the parent (App) — not as an effect, but as component state in the parent, or instance state stored in a ref, etc., and pass it to the children via props, context, a Redux store, etc.
In the below, I'll pass things to the children via props, but that's just for the purposes of an example.
State
The usual place to store information used by child elements is in the parent's state. useState supports a callback function that will only be called during initialization. That's where to put this sort of thing unless you have a good reason not to. In the comments, you've suggested you have a good reason not to, but I'd be remiss if I didn't mention it in an answer meant for others in the future, not just for you now.
(This example and the second one below pass the information to the children via props, but again, it could be props, context, a Redux store, etc.)
Example:
const { useState, useEffect } = React;
let Child2 = () => {
return <div>Child 2</div>;
};
let Child1 = ({ value, children }) => {
return (
<div>
<div>value = {value}</div>
{children}
</div>
);
};
let FirstComponent = ({ value }) => {
return <div>value = {value}</div>;
};
function App() {
const [value] = useState(() => {
// Do one-time initialization here (pretend this is an
// expensive operation).
console.log("Doing initialization");
return Math.floor(Math.random() * 1000);
});
useEffect(() => {
return () => {
// This is called on unmount, clean-up here if necessary
console.log("Doing cleanup");
};
}, []);
return (
<div>
<FirstComponent value={value} />
<Child1 value={value}>
<Child2 />
</Child1>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Technically, I suppose you could use it for doing something that didn't result in any value at all, but that would be odd semantically and I don't think I'd recommend it.
Non-State
If it's information that can't be stored in state for some reason, you can use a ref, either to store it (although then you might prefer state) or to just store a flag saying "I've done my initialization." One-time initialization of refs is perfectly normal. And if the initialization requires cleanup, you can do that in a useEffect cleanup callback. Here's an example (this example does end up storing something other than a flag on the ref, but it could just be a flag):
const { useRef, useEffect } = React;
let Child2 = () => {
return <div>Child 2</div>;
};
let Child1 = ({ value, children }) => {
return (
<div>
<div>value = {value}</div>
{children}
</div>
);
};
let FirstComponent = ({ value }) => {
return <div>value = {value}</div>;
};
function App() {
// NOTE: This code isn't using state because the OP has a reason
// for not using state, which happens sometimes. But without
// such a reason, the normal thing to do here would be to use state,
// perhaps `useState` with a callback function to do it once
const instance = useRef(null);
if (!instance.current) {
// Do one-time initialization here, save the results
// in `instance.current`:
console.log("Doing initialization");
instance.current = {
value: Math.floor(Math.random() * 1000),
};
}
const { value } = instance.current;
useEffect(() => {
return () => {
// This is called on unmount, clean-up here if necessary
console.log("Doing cleanup");
};
}, []);
return (
<div>
<FirstComponent value={value} />
<Child1 value={value}>
<Child2 />
</Child1>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Your specific example use case
Re the specific use case you linked (note: the code from the question may not be correct; I'm not trying to correct it here, not least because I don't use axios):
I am using an axios interceptor to handle errors globally, but would like to set the state of my app from the interceptor.
axios.interceptors.response.use(
error => {
AppState.setError(error)
}
)
(And you've indicated that AppState, whatever it is, is only accessible within App.)
I'm not a fan of modifying the global axios instance, it's another crosstalky thing that affects all code using axios in the page/app, regardless of whether it's your code or code in a library that may use axios in a way where it's not appropriate for your app to show an error state that occurs.
I'd lean toward decoupling the interceptor from the App state update:
Have an axios wrapper module taht exports a custom axios instance
Put the interceptor on that instance
Provide a means of subscribing to error events from that module
Have App subscribe to the error event from that to set its state (and unsubscribe on unmount)
That sounds complicated, but it's only about 30 lines of code even if you don't have a prebuilt event emitter class:
import globalAxios from "axios";
const errorListeners = new Set();
export function errorSubscribe(fn) {
errorListeners.add(fn);
}
export function errorUnsubscribe(fn) {
errorListeners.delete(fn);
}
function useErrorListener(fn) {
const subscribed = useRef(false);
if (!subscribed.current) {
subscribed.current = true;
errorSubscribe(fn);
}
useEffect(() => {
return () => errorUnsubscribe(fn);
}, []);
}
export const axios = globalAxios.create({/*...config...*/});
instance.interceptors.response.use(() => {
(error) => {
for (const listener of errorListeners) {
try { listener(error); } catch {}
}
};
});
Then in App:
import { axios, useErrorListener } from "./axios-wrapper";
function App() {
useErrorListener((error) => AppState.setError(error));
// ...
}
In code that needs to use that instance of axios:
import { axios } from "./axios-wrapper";
// ...
That's a bit barebones (it assumes you'd never have dependencies on the error callback function, for instance), but you get the idea.
I second to #T.J. Crowder, you should not rely on execution order of components to implement any feature. For reasons:
What you want to achieve is anti-pattern, implicit dependency that surprises ppl. JUST DON'T DO IT.
It's not very reliable after all. The execution order is maintained, but continuity is not guaranteed.
I'll complement his answer with some details on execution order of React components.
So for a simple case of:
function A() {
return (<>
<B />
<C>
<D />
</C>
</>
);
}
The rule of thumb to determine component execution order, is to think in terms of JSX element creation call. In our case it'll be A(B(), C(D())), Same as JS function execution order, the execution (or "render") order of components would be B, D, C, A.
But this comes with caveats:
If any component bails out, e.g., D is a React.memo'ed "pure" component and its props doesn't change, then order would be B, C, A, order is maintained, but bailout component is skipped.
Not-so-everyday exception like SuspenseList (experimental)
<SuspenseList> coordinates the “reveal order” of the closest <Suspense> nodes below it.
which of cause affects execution order of its children by design.
In concurrent mode, because rendering can be interrupted at React's discretion, the continuity of execution order is in question. Sth like B, D, B, D, C, A could happen. (That said, useEffect seems unaffected AFAICT cus it's invoked in commit phase)

React useState is called multiple times [duplicate]

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through 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. You will find a single render.
This happens is an intentional feature of the 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.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
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.
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>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
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.
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/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
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
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. Let the re-renders to be managed by 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. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
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.

Why is this console log happening twice? React rendering cycle [duplicate]

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through 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. You will find a single render.
This happens is an intentional feature of the 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.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
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.
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>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
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.
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/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
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
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. Let the re-renders to be managed by 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. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
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.

React Warning: Cannot update a component from inside the function body of a different component

I am using Redux with Class Components in React. Having the below two states in Redux store.
{ spinner: false, refresh: false }
In Parent Components, I have a dispatch function to change this states.
class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
this.props.onShowSpinner();
this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
In Child Component, I am trying to reload the parent component like below.
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
if (somecondition) {
// doing some redux store update
props.reloadApp();
}
}
render() {
return <button />;
}
}
I am getting error as below.
Warning: Cannot update a component from inside the function body of a
different component.
How to remove this warning? What I am doing wrong here?
For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:
export const useOrderbookSubscription = marketId => {
const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
variables: {
marketId,
},
})
const formattedData = useMemo(() => {
// DISPATCHING HERE CAUSED THE WARNING
}, [data])
// DISPATCHING HERE CAUSED THE WARNING TOO
// Note: Dispatching to the store has to be done in a useEffect so that React
// can sync the update with the render cycle otherwise it causes the message:
// `Warning: Cannot update a component from inside the function body of a different component.`
useEffect(() => {
orderbookStore.dispatch(setOrderbookData(formattedData))
}, [formattedData])
return { data: formattedData, error, loading }
}
If your code calls a function in a parent component upon a condition being met like this:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
Try wrapping the condition in a useEffect:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
useEffect(() => {
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
}, [data, handleNoUsersLoaded]);
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
It seems that you have latest build of React#16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.
from the docs:
It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.
Coming to the actual question.
I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.
class Child extends React.Component {
constructor(props){
super(props);
this.updateState = this.updateState.bind(this);
}
updateState() { // call this onClick to trigger the update
if (somecondition) {
// doing some redux store update
this.props.reloadApp();
}
}
render() {
return <button onClick={this.updateState} />;
}
}
Same error but different scenario
tl;dr wrapping state update in setTimeout fixes it.
This scenarios was causing the issue which IMO is a valid use case.
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setSomeState(someNewValue);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
fix
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setTimeout(() => {
setSomeState(someNewValue);
}, 0);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
In my case I had missed the arrow function ()=>{}
Instead of onDismiss={()=>{/*do something*/}}
I had it as onDismiss={/*do something*/}
I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect
Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().
import React from "react";
export default class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
// this.props.onShowSpinner();
// this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
// if (somecondition) {
// doing some redux store update
props.reloadApp();
// }
}
componentDidMount(props) {
if (props) {
props.reloadApp();
}
}
render() {
return <h1>This is a child.</h1>;
}
}
I got this error using redux to hold swiperIndex with react-native-swiper
Fixed it by putting changeSwiperIndex into a timeout
I got the following for a react native project while calling navigation between screens.
Warning: Cannot update a component from inside the function body of a different component.
I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:
const HomeScreen = ({navigation}) => {
return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) }
The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.
onPress{ () => navigation.navigate('ChatRoom')}
instead of the previous
onPress{ navigation.navigate('ChatRoom')}
so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.
PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx
If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.
I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of #Rajnikant; with some sample code.
I received the warning because of following. Note the props.setFilteredItems in the render function.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- Danger! Updates Redux during a render!
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
useEffect(() => {
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- OK now; effect runs outside of render.
}, [nameFilter, cityFilter]);
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.
I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.
Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.
Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.
Here's the link (thanks Masoud):
https://www.youtube.com/watch?v=Qf68sssXPtM
In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.
However wrapping my useEffect content in a :
setTimeout(() => {
//....
}, 0);
Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!
I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.
Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.
const showNotifyTimeout = setTimeout(() => {
this.props.showNotification();
clearTimeout(showNotifyTimeout);
}, 100);

Categories

Resources