According to the docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
We can use the new useEffect() hook to simulate componentDidUpdate(), but it seems like useEffect() is being ran after every render, even the first time. How do I get it to not run on initial render?
As you can see in the example below, componentDidUpdateFunction is printed during the initial render but componentDidUpdateClass was not printed during the initial render.
function ComponentDidUpdateFunction() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
class ComponentDidUpdateClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidUpdate() {
console.log("componentDidUpdateClass");
}
render() {
return (
<div>
<p>componentDidUpdateClass: {this.state.count} times</p>
<button
onClick={() => {
this.setState({ count: this.state.count + 1 });
}}
>
Click Me
</button>
</div>
);
}
}
ReactDOM.render(
<div>
<ComponentDidUpdateFunction />
<ComponentDidUpdateClass />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
We can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.
If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.
Example
const { useState, useRef, useLayoutEffect } = React;
function ComponentDidUpdateFunction() {
const [count, setCount] = useState(0);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<ComponentDidUpdateFunction />,
document.getElementById("app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
You can turn it into custom hooks, like so:
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) func();
else didMount.current = true;
}, deps);
}
export default useDidMountEffect;
Usage example:
import React, { useState, useEffect } from 'react';
import useDidMountEffect from '../path/to/useDidMountEffect';
const MyComponent = (props) => {
const [state, setState] = useState({
key: false
});
useEffect(() => {
// you know what is this, don't you?
}, []);
useDidMountEffect(() => {
// react please run me if 'key' changes, but not on initial render
}, [state.key]);
return (
<div>
...
</div>
);
}
// ...
I made a simple useFirstRender hook to handle cases like focussing a form input:
import { useRef, useEffect } from 'react';
export function useFirstRender() {
const firstRender = useRef(true);
useEffect(() => {
firstRender.current = false;
}, []);
return firstRender.current;
}
It starts out as true, then switches to false in the useEffect, which only runs once, and never again.
In your component, use it:
const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);
useEffect(() => {
if (firstRender || errors.phoneNumber) {
phoneNumberRef.current.focus();
}
}, [firstRender, errors.phoneNumber]);
For your case, you would just use if (!firstRender) { ....
Same approach as Tholle's answer, but using useState instead of useRef.
const [skipCount, setSkipCount] = useState(true);
...
useEffect(() => {
if (skipCount) setSkipCount(false);
if (!skipCount) runYourFunction();
}, [dependencies])
EDIT
While this also works, it involves updating state which will cause your component to re-render. If all your component's useEffect calls (and also all of its children's) have a dependency array, this doesn't matter. But keep in mind that any useEffect without a dependency array (useEffect(() => {...}) will be run again.
Using and updating useRef will not cause any re-renders.
#ravi, yours doesn't call the passed-in unmount function. Here's a version that's a little more complete:
/**
* Identical to React.useEffect, except that it never runs on mount. This is
* the equivalent of the componentDidUpdate lifecycle function.
*
* #param {function:function} effect - A useEffect effect.
* #param {array} [dependencies] - useEffect dependency list.
*/
export const useEffectExceptOnMount = (effect, dependencies) => {
const mounted = React.useRef(false);
React.useEffect(() => {
if (mounted.current) {
const unmount = effect();
return () => unmount && unmount();
} else {
mounted.current = true;
}
}, dependencies);
// Reset on unmount for the next mount.
React.useEffect(() => {
return () => mounted.current = false;
}, []);
};
a simple way is to create a let, out of your component and set in to true.
then say if its true set it to false then return (stop) the useEffect function
like that:
import { useEffect} from 'react';
//your let must be out of component to avoid re-evaluation
let isFirst = true
function App() {
useEffect(() => {
if(isFirst){
isFirst = false
return
}
//your code that don't want to execute at first time
},[])
return (
<div>
<p>its simple huh...</p>
</div>
);
}
its Similar to #Carmine Tambasciabs solution but without using state :)
function useEffectAfterMount(effect, deps) {
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) return effect();
else isMounted.current = true;
}, deps);
// reset on unmount; in React 18, components can mount again
useEffect(() => {
isMounted.current = false;
});
}
We need to return what comes back from effect(), because it might be a cleanup function. But we don't need to determine if it is or not. Just pass it on and let useEffect figure it out.
In an earlier version of this post I said resetting the ref (isMounted.current = false) wasn't necessary. But in React 18 it is, because components can remount with their previous state (thanks #Whatabrain).
I thought creating a custom hook would be overkill and I didn't want to muddle my component's readability by using the useLayoutEffect hook for something unrelated to layouts, so, in my case, I simply checked to see if the value of my stateful variable selectedItem that triggers the useEffect callback is its original value in order to determine if it's the initial render:
export default function MyComponent(props) {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {
if(!selectedItem) return; // If selected item is its initial value (null), don't continue
//... This will not happen on initial render
}, [selectedItem]);
// ...
}
This is the best implementation I've created so far using typescript. Basically, the idea is the same, using the Ref but I'm also considering the callback returned by useEffect to perform cleanup on component unmount.
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
* #param effect
* #param dependencies
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependencies?: 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;
}, dependencies);
}
You can simply use it, as usual as you use the useEffect hook but this time, it won't run on the initial render. Here is how you can use this hook.
useNoInitialEffect(() => {
// perform something, returning callback is supported
}, [a, b]);
If you use ESLint and want to use the react-hooks/exhaustive-deps rule for this custom hook:
{
"rules": {
// ...
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useNoInitialEffect"
}]
}
}
#MehdiDehghani, your solution work perfectly fine, one addition you have to do is on unmount, reset the didMount.current value to false. When to try to use this custom hook somewhere else, you don't get cache value.
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
let unmount;
if (didMount.current) unmount = func();
else didMount.current = true;
return () => {
didMount.current = false;
unmount && unmount();
}
}, deps);
}
export default useDidMountEffect;
Simplified implementation
import { useRef, useEffect } from 'react';
function MyComp(props) {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
} else {
myProp = 'some val';
};
}, [props.myProp])
return (
<div>
...
</div>
)
}
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
For people who are having trouble with React 18 strict mode calling the useeffect on the initial render twice, try this:
// The init variable is necessary if your state is an object/array, because the == operator compares the references, not the actual values.
const init = [];
const [state, setState] = useState(init);
const dummyState = useRef(init);
useEffect(() => {
// Compare the old state with the new state
if (dummyState.current == state) {
// This means that the component is mounting
} else {
// This means that the component updated.
dummyState.current = state;
}
}, [state]);
Works in development mode...
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
And in production.
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you want to skip the first render, you can create a state "firstRenderDone" and set it to true in the useEffect with empty dependecy list (that works like a didMount). Then, in your other useEffect, you can check if the first render was already done before doing something.
const [firstRenderDone, setFirstRenderDone] = useState(false);
//useEffect with empty dependecy list (that works like a componentDidMount)
useEffect(() => {
setFirstRenderDone(true);
}, []);
// your other useEffect (that works as componetDidUpdate)
useEffect(() => {
if(firstRenderDone){
console.log("componentDidUpdateFunction");
}
}, [firstRenderDone]);
All previous are good, but this can be achieved in a simplier way considering that the action in useEffect can be "skipped" placing an if condition(or any other ) that is basically not run first time, and still with the dependency.
For example I had the case of :
Load data from an API but my title has to be "Loading" till the date were not there, so I have an array, tours that is empty at beginning and show the text "Showing"
Have a component rendered with different information from those API.
The user can delete one by one those info, even all making the tour array empty again as the beginning but this time the API fetch is been already done
Once the tour list is empty by deleting then show another title.
so my "solution" was to create another useState to create a boolean value that change only after the data fetch making another condition in useEffect true in order to run another function that also depend on the tour length.
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
here my App.js
import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'
const url = 'API url'
let newTours
function App() {
const [loading, setLoading ] = useState(true)
const [tours, setTours] = useState([])
const [isTitle, isSetTitle] = useState(false)
const [title, setTitle] = useState("Our Tours")
const newTitle = "Tours are empty"
const removeTours = (id) => {
newTours = tours.filter(tour => ( tour.id !== id))
return setTours(newTours)
}
const changeTitle = (title) =>{
if(tours.length === 0 && loading === false){
setTitle(title)
}
}
const fetchTours = async () => {
setLoading(true)
try {
const response = await fetch(url)
const tours = await response.json()
setLoading(false)
setTours(tours)
}catch(error) {
setLoading(false)
console.log(error)
}
}
useEffect(()=>{
fetchTours()
},[])
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
if(loading){
return (
<main>
<Loading />
</main>
)
}else{
return (
<main>
<Tours tours={tours} title={title} changeTitle={changeTitle}
removeTours={removeTours} />
</main>
)
}
}
export default App
const [dojob, setDojob] = useState(false);
yourfunction(){
setDojob(true);
}
useEffect(()=>{
if(dojob){
yourfunction();
setDojob(false);
}
},[dojob]);
I have an 'Accept' button which I would like to be automatically clicked after 5 seconds. I'm using React with Next.js. The button code is:
<button name="accept" className="alertButtonPrimary" onClick={()=>acceptCall()}>Accept</button>
If I can't do this, I would like to understand why, so I can improve my React and Next.js skills.
I'm guessing you want this activated 5 seconds after render, in that case, put a setTimeout inside of the useEffect hook, like so. this will call whatever is in the hook after the render is complete.
Although this isn't technically activating the button click event.
useEffect(() => {
setTimeout(() => {
acceptCall()
}, timeout);
}, [])
in that case you should use a ref like so,
const App = () => {
const ref = useRef(null);
const myfunc = () => {
console.log("I was activated 5 seconds later");
};
useEffect(() => {
setTimeout(() => {
ref.current.click();
}, 5000); //miliseconds
}, []);
return (
<button ref={ref} onClick={myfunc}>
TEST
</button>
);
};
Hopefully, this is what you are looking for.
https://codesandbox.io/s/use-ref-forked-bl7i0?file=/src/index.js
You could create a ref for the <button> and set a timeout inside of an effect hook to call the button click event after 5 seconds.
You could throw in a state hook to limit the prompt.
import React, { useEffect, useRef, useState } from "react";
const App = () => {
const buttonRef = useRef("accept-button");
const [accepted, setAccepted] = useState(false);
const acceptCall = (e) => {
alert("Accepted");
};
const fireEvent = (el, eventName) => {
const event = new Event(eventName, { bubbles: true });
el.dispatchEvent(event);
};
useEffect(() => {
if (!accepted) {
setTimeout(() => {
if (buttonRef.current instanceof Element) {
setAccepted(true);
fireEvent(buttonRef.current, "click");
}
}, 5000);
}
}, [accepted]);
return (
<div className="App">
<button
name="accept"
className="alertButtonPrimary"
ref={buttonRef}
onClick={acceptCall}
>
Accept
</button>
</div>
);
};
export default App;
My React game has a <Clock/> component to keep track of the time.
The timer should stop when the game is paused.
I am using Redux to manage the play/pause state, as well as the elapsed time.
const initialState = { inProgress: false, timeElapsed: 0 }
The inProgress state is handled by a button on another component, which dispatches an action to update the store (for the inProgress value only).
The <Clock/> component increments timeElapsed in its useEffect hook with setInterval. Yet it does not clear.
import React from 'react';
import { connect } from 'react-redux';
const Clock = ({ dispatch, inProgress, ticksElapsed }) => {
React.useEffect(() => {
const progressTimer = setInterval(function(){
inProgress ? dispatch({ type: "CLOCK_RUN" }) : clearInterval(progressTimer);
}, 1000)
}, [inProgress]);
return (
<></>
)
};
let mapStateToProps = ( state ) => {
let { inProgress, ticksElapsed } = state.gameState;
return { inProgress, ticksElapsed };
}
export default connect(
mapStateToProps,
null,
)(Clock);
Inside setInterval, when inProgress is false, I would expect clearInterval(progressTimer) to stop the clock.
Also, there is another issue where leaving out the [inProgress] in the useEffect hook causes the timer to increment at ridiculous rates, crashing the app.
Thank you.
The inProgress is a stale closure for the function passed to setInterval.
You can solve it by clearing the interval in the cleanup function:
const Clock = ({ dispatch, inProgress, ticksElapsed }) => {
React.useEffect(() => {
const progressTimer = setInterval(function () {
inProgress && dispatch({ type: 'CLOCK_RUN' });
}, 500);
return () =>
//inprogress is stale so when it WAS true
// it must now be false for the cleanup to
// be called
inProgress && clearInterval(progressTimer);
}, [dispatch, inProgress]);
return <h1>{ticksElapsed}</h1>;
};
const App = () => {
const [inProgress, setInProgress] = React.useState(false);
const [ticksElapsed, setTicksElapsed] = React.useState(0);
const dispatch = React.useCallback(
() => setTicksElapsed((t) => t + 1),
[]
);
return (
<div>
<button onClick={() => setInProgress((p) => !p)}>
{inProgress ? 'stop' : 'start'}
</button>
<Clock
inProgress={inProgress}
dispatch={dispatch}
ticksElapsed={ticksElapsed}
/>
</div>
);
};
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>
I'm trying to implement a loader using Next.js and hooks.
I have to do a fetch to check new data every 3 minutes and when that happens, I want to use a loading state.
I created a setLoading that starts at false and inside a setInterval pass to true, but I don't 'know why doesn't work.
Here is my code:
import React, { useState, useEffect } from "react";
import Context from "../../config/Context";
import { checkNewData } from "../../helper/index";
function Home() {
const [contentLoading, setLoading] = useState(false);
const data = context.events[0];
useEffect(() => {
setInterval(() => {
if (data.live == false) {
setLoading(true);
checkNewData();
}
setLoading(false)
}, 10000);
return (
<React.Fragment>
{contentLoading ? <p>loading</p> : <p>no loading</p>
</React.Fragment>
);
}
export default Home;
Ok, so here You have working interval
(Every 3 sec calling api, and counts how many times it works(not needed just FYI)
In the function responsible for Api call you should turn on the loader - then every 3 seconds the data is called again
const Home = (props) => {
const [loading, setLoading] = useState(false);
const [check, setCheck] = useState(0)
const callApi = () => {
Here you should call your api + set loader
if fetching(setLoading(true))
if fetched(setLoading(false))
}
useEffect(() => {
const id = setInterval(() => {
callApi()
setCheck(check + 1)
}, 3000);
return () => clearInterval(id);
}, [check])
return (
<React.Fragment>
{loading ? <p>Loading</p> : <p>No Loading</p>}
<p>Times check execute {check}</p>
</React.Fragment>
);
}
A better way to do this now will be to use a plugin like SWR. It will handle the data fetch and even the loading state seamlessly.
Check the documentation here: https://swr.vercel.app
I currently have a component that does a history.push('/') after a few seconds. But I am getting warnings
index.js:1375 Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
and also
index.js:1375 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I am fairly new to React, do I need do some sort of clean up?
Here is my component.
import React, {useState} from 'react'
import {UsePostOrPutFetch} from "../hooks/postHook";
import "./styles/ConfirmationChange.scss";
import moment from 'moment';
export default function ConfirmatonChange(props) {
const [shouldFetch, setShouldFetch] = useState(false);
const [data,loading,isError, errorMessage] = UsePostOrPutFetch("/send-email/", props.data.location.state.value,"POST", shouldFetch, setShouldFetch);
const [countDown, setCountDown] = useState(5)
let spinner = (
<strong className="c-spinner" role="progressbar">
Loading…
</strong>
);
const changeView = () =>
{
if (countDown < 0) {
props.data.history.push('/')
} else {
setTimeout(() => {
setCountDown(countDown - 1)
}
, 1000)
}
}
return (
<div>
<div className="o-container">
<article className="c-tile">
<div className="c-tile__content">
<div className="c-tile__body u-padding-all">
<button className = "c-btn c-btn--primary u-margin-right" onClick={props.data.history.goBack}>Edit</button>
<button className = "c-btn c-btn--secondary u-margin-right" disabled={loading} onClick={(e) => { setShouldFetch(true)}}>Send</button>
{!loading && data === 200 && !isError ? (
<div className="ConfirmationChange-success-send">
<hr hidden={!data === 200} className="c-divider" />
Email Sent succesfully
<p>You will be redirected shortly....{countDown}</p>
{changeView()}
</div>
) : (loading && !isError ? spinner :
<div className="ConfirmationChange-error-send">
<hr hidden={!isError} className="c-divider" />
{errorMessage}
</div>
)}
</div>
</div>
</article>
</div>
</div>
)
}
And here is what my data fetch component look like
import { useState, useEffect } from "react";
import { adalApiFetch } from "../config/adal-config";
const UsePostOrPutFetch = (url, sendData, methodType, shouldFetch, setShouldSend) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [errorMessage, setError] = useState("");
useEffect(() => {
const ac = new AbortController();
if (shouldFetch) {
const postOrPutData = async () => {
try {
const response = await adalApiFetch(fetch, url,
{
signal: ac.signal,
method: methodType,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(sendData)
});
const json = await response.json();
setData(await json);
setLoading(true);
} catch (err) {
setIsError(true);
setError(err.message);
} finally {
setShouldSend(false)
setLoading(false);
}
};
postOrPutData();
}
return () => { ac.abort(); };
}, [shouldFetch, sendData, url, methodType, setShouldSend]);
return [data, loading, isError, errorMessage];
};
export {UsePostOrPutFetch}
Any help would be greatly appreciated.
Check React Hooks - Check If A Component Is Mounted
The most common cause of this warning is when a user kicks off an asynchronous request, but leaves the page before it completes.
You'll need a componentIsMounted variable and useEffect and useRef hooks :
const componentIsMounted = useRef(true);
useEffect(() => {
return () => {
componentIsMounted.current = false;
};
}, []);
const changeView = () => {
if (countDown < 0) {
props.data.history.push("/");
} else {
setTimeout(() => {
if (componentIsMounted.current) { // only update the state if the component is mounted
setCountDown(countDown - 1);
}
}, 1000);
}
};
You should do the same for data fetch component
Yup, you have a timeOut that could potentially fire after the component has unmounted.
You need to add a useEffect that clears the timer onUnmount as follows
const timerRef = useRef();
useEffect(() => () => clearTimeout(timerRef.current), [])
const changeView = () => {
if (countDown < 0) {
props.data.history.push("/");
} else {
timerRef.current = setTimeout(() => {
setCountDown(countDown - 1);
}, 1000);
}
};