The useEffect React hook will run the passed-in function on every change. This can be optimized to let it call only when the desired properties change.
What if I want to call an initialization function from componentDidMount and not call it again on changes? Let's say I want to load an entity, but the loading function doesn't need any data from the component. How can we make this using the useEffect hook?
class MyComponent extends React.PureComponent {
componentDidMount() {
loadDataOnlyOnce();
}
render() { ... }
}
With hooks this could look like this:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire on every change :(
}, [...???]);
return (...);
}
If you only want to run the function given to useEffect after the initial render, you can give it an empty array as second argument.
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce();
}, []);
return <div> {/* ... */} </div>;
}
TL;DR
useEffect(yourCallback, []) - will trigger the callback only after the first render.
Detailed explanation
useEffect runs by default after every render of the component (thus causing an effect).
When placing useEffect in your component you tell React you want to run the callback as an effect. React will run the effect after rendering and after performing the DOM updates.
If you pass only a callback - the callback will run after each render.
If passing a second argument (array), React will run the callback after the first render and every time one of the elements in the array is changed. for example when placing useEffect(() => console.log('hello'), [someVar, someOtherVar]) - the callback will run after the first render and after any render that one of someVar or someOtherVar are changed.
By passing the second argument an empty array, React will compare after each render the array and will see nothing was changed, thus calling the callback only after the first render.
useMountEffect hook
Running a function only once after component mounts is such a common pattern that it justifies a hook of its own that hides implementation details.
const useMountEffect = (fun) => useEffect(fun, [])
Use it in any functional component.
function MyComponent() {
useMountEffect(function) // function will run only once after it has mounted.
return <div>...</div>;
}
About the useMountEffect hook
When using useEffect with a second array argument, React will run the callback after mounting (initial render) and after values in the array have changed. Since we pass an empty array, it will run only after mounting.
We have to stop thinking in component-life-cycle-methods (i.e. componentDidMount). We have to start thinking in effects. React effects are different from old-style class-life-cycle-methods.
By default effects run after every render cycle, but there are options to opt out from this behaviour. To opt out, you can define dependencies that mean that an effect is only carried out when a change to one of the dependencies is made.
If you explicitly define, that an effect has no dependecy, the effect runs only once, after the first render-cycle.
1st solution (with ESLint-complaint)
So, the first solution for your example would be the following:
function MyComponent() {
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, []);
return (...);
}
But then the React Hooks ESLint plugin will complain with something like that:
React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.
At first this warning seems annoying, but please don't ignore it. It helps you code better and saves you from "stale closures". If you don't know what "stale closures" are, please read this great post.
2nd solution (the right way, if dependency is not dependent on component)
If we add loadDataOnlyOnce to the dependency array, our effect will run after every render-cycle, because the reference of loadDataOnlyOnce changes on every render, because the function is destroyed(garbarge-collected) and a new function is created, but that's exactly what we don't want.
We have to keep the same reference of loadDataOnlyOnce during render-cycles.
So just move the function-definition above:
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, [loadDataOnlyOnce]);
return (...);
}
With this change you ensure that the reference of loadDataOnlyOnce will never change. Therefore you can also safely add the reference to the dependency array.
3rd solution (the right way, if dependency is dependent on component)
If the dependency of the effect (loadDataOnlyOnce), is dependent on the component (need props or state), there's React's builtin useCallback-Hook.
An elementary sense of the useCallback-Hook is to keep the reference of a function identical during render-cycles.
function MyComponent() {
const [state, setState] = useState("state");
const loadDataOnlyOnce = useCallback(() => {
console.log(`I need ${state}!!`);
}, [state]);
useEffect(() => {
loadDataOnlyOnce(); // this will fire only when loadDataOnlyOnce-reference changes
}, [loadDataOnlyOnce]);
return (...);
}
function useOnceCall(cb, condition = true) {
const isCalledRef = React.useRef(false);
React.useEffect(() => {
if (condition && !isCalledRef.current) {
isCalledRef.current = true;
cb();
}
}, [cb, condition]);
}
and use it.
useOnceCall(() => {
console.log('called');
})
or
useOnceCall(()=>{
console.log('Fetched Data');
}, isFetched);
Pass an empty array as the second argument to useEffect. This effectively tells React, quoting the docs:
This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
Here's a snippet which you can run to show that it works:
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUser(data.results[0]);
});
}, []); // Pass empty array to only run once on mount.
return <div>
{user ? user.name.first : 'Loading...'}
</div>;
}
ReactDOM.render(<App/>, 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>
I like to define a mount function, it tricks EsLint in the same way useMount does and I find it more self-explanatory.
const mount = () => {
console.log('mounted')
// ...
const unmount = () => {
console.log('unmounted')
// ...
}
return unmount
}
useEffect(mount, [])
leave the dependency array blank . hope this will help you understand better.
useEffect(() => {
doSomething()
}, [])
empty dependency array runs Only Once, on Mount
useEffect(() => {
doSomething(value)
}, [value])
pass value as a dependency. if dependencies has changed since the last time, the effect will run again.
useEffect(() => {
doSomething(value)
})
no dependency. This gets called after every render.
I had this issue with React 18. This is how I handled it:
import { useEffect, useRef } from "react";
export default function Component() {
const isRunned = useRef(false);
useEffect(() => {
if(isRunned.current) return;
isRunned.current = true;
/* CODE THAT SHOULD RUN ONCE */
}, []);
return <div> content </div>;
}
Check here how they explain why useEffect is called more than once.
Here is my version of Yasin's answer.
import {useEffect, useRef} from 'react';
const useOnceEffect = (effect: () => void) => {
const initialRef = useRef(true);
useEffect(() => {
if (!initialRef.current) {
return;
}
initialRef.current = false;
effect();
}, [effect]);
};
export default useOnceEffect;
Usage:
useOnceEffect(
useCallback(() => {
nonHookFunc(deps1, deps2);
}, [deps1, deps2])
);
This does not answer your question exactly but may have the same intended affect of only running a function once and after the first render. Very similar to the componentDidMount function. This uses useState instead of useEffect to avoid dependency lint errors. You simply pass a self-executing anonymous function as the first argument to useState. As an aside, I'm not sure why React doesn't simply provide a hook that does this.
import React, { useState } from "react"
const Component = () => {
useState((() => {
console.log('componentDidMountHook...')
}))
return (
<div className='component'>Component</div>
)
}
export default Component
I found out after some time spend on the internet. useEffect fires once on component mount, then componennt unmounts and mounts again, useEffect fires again. You have to check more on React docs, why they do that.
So, I used custom hook for that. On unmount you have to change your useRef state. In this case do not forget a return statement: when component unmounts, useEffect runs cleanup function after return.
import React, { useEffect, useRef } from "react"
const useDidMountEffect = (
func: () => void,
deps: React.DependencyList | undefined
) => {
const didMount = useRef(false)
useEffect(() => {
if (didMount.current) {
func()
}
return () => {
didMount.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
}
export default useDidMountEffect
Use it like normal useEffect:
useDidMountEffect(() => {
// your action
}, [])
window.onpageshow works even if the user presses the back button to navigate to the page, unlike passing an empty array as second argument of the use-effect hook which does not fire when returning to the page via the back button (thus not on every form of initial page load).
useEffect(() => {
window.onpageshow = async function() {
setSomeState(false)
let results = await AsyncFunction()
console.log(results, 'Fires on on first load,
refresh, or coming to the page via the back button')
};
};
I found that with the once function from lodash the problem may be solved concisely and elegantly.
import { once } from "lodash";
import { useEffect, useRef } from "react";
export const useEffectOnce = (cb: () => void) => {
const callBackOnce = useRef(once(cb)).current;
useEffect(() => callBackOnce(), [callBackOnce]);
};
Incase you just call the function in useeffect after render you add an empty array as the second argument for the useeffect
useEffect=(()=>{
functionName(firstName,lastName);
},[firstName,lastName])
A class component and a function component, both of which can click on the div to modify the value of num.
export default function App() {
const [num, setNum] = useState(0);
const click = () => {
setTimeout(() => {
console.log(num, "3000");
}, 3000);
setNum(num + 1);
};
return <div onClick={click}>click {num}</div>;
}
export default class App extends React.Component {
state = {
num: 0
};
click = () => {
setTimeout(() => {
console.log(this.state.num);
}, 3000);
this.setState({ num: this.state.num + 1 });
};
render() {
return <div onClick={this.click}>click {this.state.num}</div>;
}
}
Why is the output of num different in the above two components after clicking
In class components, when state is set, when the component updates, the new state will be assigned to the state property of the instance. To oversimplify, doing
this.setState(someNewState);
eventually results in React doing something like
theInstance.state = mergedNewState;
As a result, referencing this.state will result in changes being seen if the state updates, because this.state points to something new..
Functional components are different. Unlike class components, nothing gets (visibly) mutated when using their state. Instead, when you call a state setter, the whole function (component) runs again, and the calls to useState return a new value.
In your code, the
const [num, setNum] = useState(0);
results in a num that the click function closes over.. If there's a click, when the timeout callback runs, it'll still close over the same num that was created before the click handler runs. The component will re-render with a new click function, which has a new num number that it closes over, but the old click function will still be referencing the num from the render in which that old function was created.
I currently have the following useState and function:
const [displayTraits, setDisplayTraits] = useState(false);
const feelingsFilled = () => {
const keysToCheck = ["anxiety", "mood", "cognition"];
function validate(obj) {
try {
if (
Object.keys(obj)
.filter((key) => keysToCheck.includes(key))
.every((key) => obj[key].counter > 0)
) {
setDisplayTraits(true);
} else {
setDisplayTraits(false);
}
} catch (err) {
console.log("error");
}
}
validate(daily);
};
feelingsFilled();
which I then try to hook up with a modal so that when my function feelingsFilled() return true and changes the displayTraits to true, then it will open.
<Modal isVisible={displayTraits} />
I am trying to run this but get the following error
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I would need to make some assumptions on where the daily data comes from. If it is a changing prop or some other state, you can compute stuff based on that.
Using some state:
useEffect(() => {
//computeYourStuffAndSetState
const result = validate(daily);
setDisplayTraits(result);
}, [daily]);
// The array param is a change-listener
Then you can bind your <Modal visible directly to the state variable, instead of a function.
Another example is to not use state at all and compute it everytime daily changes.
const showModal = useMemo(() => {
return validate(daily);
}, [daily]);
useMemo and useEffect is a part of the built in react hooks.
Or you can just do something like:
const showModal = validate(daily);
This will also work, but will be less performant as it will recompute on every render
I have a component that I can use multiple times on a page. What it does is make an external call and save a value from that external call to redux store in a key object. I only want the component to do this once so I was using the componentDidMount. Now if the same component gets used again on the page I don't want it to do the external call again. This works correctly using Classes but when I try to use function hooks this no longer works.
Let me start with showing you the Class based code.
class MyComponent extends Component {
componentDidMount() {
setTimeout(() => this.wait(), 0);
}
wait() {
const { key, map } = this.props;
if (map[key] === undefined) {
saveKey(key);
console.log('LOAD EXTERNAL ID ONLY ONCE');
externalAPI(this.externalHandler.bind(this));
}
}
externalHandler(value) {
const { key, setValue } = this.props;
setValue(key, value);
}
render() {
const { key, map children } = this.props;
return (
<>
{children}
</>
);
}
}
mapStateToProps .....
export default connect(mapStateToProps, { saveKey, setValue })(MyComponent);
Reducer.js
export default (state = {}, action = null) => {
switch (action.type) {
case SAVE_KEY: {
return {
...state,
[action.id]: 'default',
};
}
case SET_VALUE: {
const { id, value } = action;
return {
...state,
[id]: value,
};
}
default: return state;
}
};
Page.js
Calls each component like below.
import React from 'react';
const Page = () => {
return (
<>
<MyComponent key='xxxxx'>First Component</MyComponent>
<MyComponent key='xxxxx'>Second Component</MyComponent>
</>
);
};
export default Page;
The above all works. So when the first component mounts i delay a call to redux, not sure why this works but it does. Can someone tell me why using the setTimeout works??? and not using the setTimeout does not. By works I mean with the timeout, first component mounts sets key because map[key] === undefined. Second component mounts map[key] is no longer undefined. But without the timeout map[key] is always === undefined ?
It stores the passed key prop in redux. The Second component mounts and sees the same key is stored so it doesn't need to call the external API getExternalID again. If a third component mounted with a different key then it should run the external API call again and so on.
As I said the above all works except I'm not sure why I needed to do a setTimout to get it to work.
Second question turning this into a function and hooks instead of a Class. This does not work for some reason.
import React, { useEffect } from 'react';
const MyComponent = ({ children, key, map, saveKey, setValue }) => {
useEffect(() => {
setTimeout(() => delay(), 0);
}, [map[key]]);
const delay = () => {
if (map[key] === undefined) {
saveKey(key);
console.log('LOAD VARIANT ONLY ONCE');
externalAPI(externalHandler);
}
};
const externalHandler = (value) => {
setValue(key, value);
};
return (
<>
{children}
</>
);
};
export default MyComponent;
First question:
Javascript works with a single thread, so even when you use delay 0 ms, the method is called after React's render method exit. Here is an experiment that I believe explains that for that :
function log(msg){
$("#output").append("<br/>"+msg);
}
function render(){
log("In render");
log("Delayed by 0 ms without setTimeout...")
setTimeout(() =>log("Delayed by 0 ms with setTimeout..."), 0);
for(var i = 0;i<10;i++) log("Delay inside render "+i);
log("Render finish");
}
render();
https://jsfiddle.net/hqbj5xdr/
So actually all the components render once before they start checking if map[key] is undefined.
Second question:
In your example, it is not really clear where map and saveKey come from. And how map is shared between components...
Anyway, a naive solution is to directly update the map. And make sure that all component
refers to this map instance (not a copy).
if (map[key] === undefined) {
map[key]=true;
saveKey(key);
console.log('LOAD VARIANT ONLY ONCE');
externalAPI(externalHandler);
}
But that is a little bad (sharing reference). So a better design may be to use a cache. Don't pass the map, but a getter and a setter. getKey and saveKey. Underlying those methods may use a map to persist which key has been set.
Let's say I have a React element <Parent> and I need to render an array of <FooBar>
My code currently looks something like
Parent = React.createClass({
getChildren() {
var children = this.props.messages; // this could be 100's
return children.map((child) => {
return <FooBar c={child} />;
});
},
render() {
return (
<div>
{this.getChildren()}
</div>
);
}
});
This is really slow when there are 100's of children because Parent waits for all the children to render. Is there a workaround to render the children incrementally so that Parent does not have to wait for all its children to render?
You can take a subset of messages at a time for render, and then queue up further updates with more children via a count in state.
Using requestIdleCallback or setTimeout to queue will allow you to escape React's state batching from intercepting the current browser paint, which would be a problem if you did setState directly from componentDidUpdate
Heres something to get you going
const Parent = React.createClass({
numMessagesPerRender = 10
constructor(props) {
super(props)
this.state = {
renderedCount: 0
}
}
componentWillReceiveProps(props) {
// must check that if the messages change, and reset count if you do
// you may also need to do a deep equality of values if you mutate the message elsewhere
if (props.messages !== this.props.messages) {
this.setState({renderedCount: 0})
}
}
getChildren() {
// take only the current
const children = this.props.messages.slice(0, this.state.renderedCount);
return children.map(child => <FooBar c={child} />);
},
render() {
return (
<div>
{this.getChildren()}
</div>
);
}
renderMoreMessagesPlease() {
// you MUST include an escape condition as we are calling from `componentDidXYZ`
// if you dont your component will get stuck in a render loop and crash
if (this.state.renderedCount < this.props.messages.length) {
// queue up state change until the frame has been painted
// otherwise setState can halt rendering to do batching of state changes into a single
// if your browser doesnt support requestIdleCallback, setTimeout should do same trick
this.idleCallbackId = requestIdleCallback(() => this.setState(prevState => ({
renderedCount: prevState.renderedCount + this.numMessagesPerRender
})))
}
}
componentDidMount() {
this.renderMoreMessagesPlease()
}
componentDidUpdate() {
this.renderMoreMessagesPlease()
}
componentDidUnmount() {
// clean up so cant call setState on an unmounted component
if (this.idleCallbackId) {
window.cancelIdleCallback(this.idleCallbackId)
}
}
});