Several years ago, I wrote an article about how to create constructor-like functionality in React with function-based components. (You can read it here: https://dev.to/bytebodger/constructors-in-functional-components-with-hooks-280m) I won't repeat the entire contents of that article, but the basic premise was this:
Class-based React components give you the ability to use a constructor.
Function-based React components have no direct equivalent to the constructor. (Because functions, by their very nature, don't have constructors.)
The core React documentation implies that you simply don't need constructors anymore. And to be fair, usually you don't need them. But it's myopic to imply that no developer, writing function-based components, will EVER need to use constructor-like functionality.
Also to be clear, this is what I'm defining as a "constructor":
Code that runs before anything else in the life-cycle of this component.
Code that runs once, and only once, for the entire life-cycle of this component.
In that article, I demonstrated a technique to "spoof" constructor-like functionality in function-based components. In fact, I even created an NPM package for my useConstructor()
feature.
Some of you may scoff at this idea, owing to React's official stance that constructors simply aren't needed - at all - in modern React applications. However, a funny thing happened after I published that article.
That article became my second-most-read post. To-date, it's received almost 90,000 views. In case it's not clear, this is a strong indication that:
Many people still wonder how to implement constructor-like functionality in function-based React components (regardless of the fact that the core documentation claims that you don't need them at all).
And apparently, there are many people out there Googling on this subject. Because most of the views that I've received on that article have come, in a steady stream, over many months (and... years) since I wrote the original article.
So why am I writing on the subject again???
Well, it turns out that my first solution was not "bad". But it was also, umm... suboptimal. Overly complicated. A bit convoluted, if you will. So I'm going to show you a simpler way to create "constructors" in function-based React components, without leveraging state values or NPM packages.
The Sample Application
(NOTE: If you want to see a live example of all the subsequent code, you can check it out here: https://stackblitz.com/edit/constructor-functionality-in-react-functional-components)
Our dead-simple example will just have one component that looks like this:
export const App = () => {
const [counter, setState] = useState(0);
const increment = () => setState(counter + 1);
return <>
<div>
Counter: {counter}
</div>
<div>
<button onClick={increment}>
Increment
</button>
</div>
<Child/>
</>;
};
This is just your standard demo/counter app. It renders a state value for the current value of counter
. And it gives you a button that you can click to increment that value.
So let's imagine that you have some bit of "pre-processing" that you want to happen the first time that <App/>
is invoked. Remember, in keeping with our guidelines for what a constructor should do, we want this pre-processing to happen before anything else in the component. (Essentially, we want the pre-processing to occur before the initial render.) We also want the pre-processing to run once, and only once, for the entire lifecycle of the component. This is where useRef()
will come in handy.
Potential Solutions
A ref is a value that remains in memory between renders. It's not the same as a state variable - because updating state variables triggers the reconciliation process. In other words, you use state variables when you have values that will influence the display. But you use refs when you have values that should stay in memory - but are not used in the display.
Utilizing useRef()
, our proposed constructor would look like this:
export const App = () => {
const [counter, setState] = useState(0);
const constructorHasRun = useRef(false);
const constructor = () => {
if (constructorHasRun.current !== false)
return;
constructorHasRun.current = true;
// put your constructor code HERE
console.log('constructor invoked at ', window.performance.now());
};
constructor();
const increment = () => setState(counter + 1);
return <>
<div>
Counter: {counter}
</div>
<div>
<button onClick={increment}>
Increment
</button>
</div>
</>;
};
When the app loads, you can see in the console that the constructor only runs once. No matter how many times you click the "Increment" button thereafter, the constructor logic is never triggered again.
[NOTE: If you are in strict mode, React will call this function twice. That can lead to some confusion while you're developing. This is expected behavior that React uses to "help you find accidental impurities". This is development-only behavior and does not affect production.]
The constructorHasRun
ref value serves as a tracking variable. Once it's set from false
to true
, the constructor code will never run again for the lifecycle of the app.
This approach works just fine - but it still feels a little clunky to me. Under this approach, you must first define the constructor function, and then remember to invoke it somewhere before the return
statement. Ideally, you'd probably like your constructor code to just... run. Luckily, we can achieve this outcome by using an Immediately Invoked Function Expression (IIFE). That would look like this:
export const App = () => {
const [counter, setState] = useState(0);
const constructorHasRun = useRef(false);
(() => {
if (constructorHasRun.current !== false)
return;
constructorHasRun.current = true;
// put your constructor code HERE
console.log('constructor invoked at ', window.performance.now());
})();
const increment = () => setState(counter + 1);
return <>
<div>
Counter: {counter}
</div>
<div>
<button onClick={increment}>
Increment
</button>
</div>
</>;
};
The Best Solution
I added this section after originally publishing this article because @miketalbot recommended this in the comments. And honestly, it's cleaner, simpler, and just more elegant than using a tracking variable with useRef()
. Here's his recommendation:
export const App = () => {
const [counter, setState] = useState(0);
useMemo(() => {
// put your constructor code HERE
console.log('constructor invoked at ', window.performance.now());
}, []);
const increment = () => setState(counter + 1);
return <>
<div>
Counter: {counter}
</div>
<div>
<button onClick={increment}>
Increment
</button>
</div>
</>;
};
This works so well because useMemo()
is used to cache the result of a computation. That means that, in order to first create the cache, useMemo()
will first run the code. Then, because the dependency array is empty, the cached computation will never be re-run again.
Top comments (23)
I always use:
useMemo(()=>doThisOnceRightNow(), [])
Oooh, I like this.
BTW, I even added this at the end of the article as the best solution (and credited you).
But this is wrong.
useMemo is a cache layer, no guaranty your useMemo will not be trigerred twice if the cache get evicted (which it does if you use suspended components or react 18 concurrent rendering).
Check useMemo docs about useMemo and suspend :
beta.reactjs.org/reference/react/u...
And also this issue that confirm this behaviour: github.com/facebook/react/issues/1...
OK, correct me if I'm wrong (and I very well might be), but it seems that both of the links you provide ultimately resolve to the issue of these items being called twice in development mode???
No, read carrefully :
OK, but...
(And I'm not trying to be resistant here - I'm honestly interested in understanding this process as well as I can.) What exactly makes makes the component "suspend" during the initial mount? And if it did suspend, then doesn't it make logical sense that you would want that function to be called again when it re-mounts??
Specifically, it says, "This should match your expectations if you rely on useMemo solely as a performance optimization."
But this is (sorta) a "performance optimization". Yes, there's a procedural aspect to it as well (as in, I want this logic to be called before the render), but there's also a performance aspect (as in, I only want this logic to be called once for the entire lifecycle of the component).
using suspense + throw a promise.
Maybe yes. Depends on what you wanted to do on construction. but if no side effects (no api call, no logging, etc) you're of course good to execute it twice.
So still be carreful, because they say this :
This means, cache can be evicted in the future without even unmounting.
I wanted to higlight a potential unexpected behaviour like the one reported on the issue.
So me saying "this is wrong" was a bit too harsh. A better answer would have been: "be aware of the caveats, it's not bullet proof".
Ohhhh, I totally get it. And I appreciate it. I've been doing React dev now for 6 years and sometimes I get to feeling like I know it all. And then... I'm slapped back into reality.
So I definitely appreciate the clarifications. Yeah... "this is wrong" may have been an overly-strong retort. But it's good as an impetus to do a "deeper dive".
Thanks!
Agreed, given that throwing an exception will unwind the state I expected that all state stored things go away (as your link seems to confirm), given that the useState initializer and the useMemo get re-run I'm presuming that the useRefs also get a new state - do you know if they do? Effectively Suspense is catching an expectionhigher up - to my understanding, the component is obliterated and is going to be initialised again in that case.
Suspense boundary within the component or an awareness that suspending causes remounting are the ways perhaps?
That's basically the React Hooks version of an IIFE singleton.
Hello adam,
Nice tip, i would add some more :
useLayoutEffect
don't answer your need first.useState
initial function alternative :and use it like this :
This is essentially what I did in my previous approach with
useConstructor()
. Granted, in my original approach, I used a state variable as the tracking variable. But I've since updated it to use a ref.Also, although I like
useLayoutEffect()
, I'm resistant to use it much because it delays display features.Hi and thank you for your article!
What is the main benefit of using this technique over using a
useEffect
?I believe you could have a code that runs when the component gets initialized using this piece of code.
useEffect()
is the out-of-the-box "answer" given in the React documentation for how to handle constructor-like functionality in a functional component. For the most part... they're right. But it depends on how specific you feel about the need for true "constructor-like" functionality.In the example you've given, that code will indeed run once, and only once, for the entire lifecycle of the component. This is ensured by the fact that you've given it an empty dependency array.
However, effects always run after the rendering cycle. Granted, in most cases, it's probably sufficient to simply use
useEffect()
and allow it to do it's processing after the rendering cycle. But in the opening comments to this article (and in its predecessor), I stated that I want my "constructor" to run:before anything else in the life-cycle of this component
If you truly want that block of code to run before the rendering cycle,
useEffect()
will not help you.Thanks for your reply!
Do you have any real-world use case scenarios that may help us understand where a
useEffect
may be less interesting to use than the solution you provided?Of course, it's gonna be on a case-by-case basis. But where I originally ran into this was when I was trying to launch API calls - calls that I knew may be a bit "laggy", so I wanted them to be launched before the render cycle.
Another use-case that I've run into is where you need to do pre-processing of variables that go beyond simply setting the initial value of a state variable. In old-skool React (meaning: class-based React components), the most common use of the constructor was to set the initial value of state variables. And in React's documentation, they talk confidently about how you don't need a constructor anymore because you can use
const [someVar, setSomeVar] = useState('theInitialValueOfSomeVar');
.But what if you're trying to initialize variables that aren't state variables? What if you want to ensure that some "tracking" variables are set to the proper state before the component even starts doing it's work?
Again... I'll stress that this is actually an "edge case". For the vast majority of components you create, you'll never need a "true" constructor-like functionality. But it's a bit silly to assume that there's never any more need for a constructor just because you can now use
useState('initial value')
.@amin had the same question I had, but since your answer was as thorough as it was, I didn't need to ask it (for which I owe you thanks!)
Perhaps
useState
itself fits better. It inherits the functionality of data initialization inside constructors of the component classes, is executed once, and does not depend on the parameters or decisions of React to flush the cache.The base use would be:
If need to get initialized data, return it as object
Ahhh, that's another interesting approach. I hadn't even considered using a function call in the constructor of
useState()
. Thanks!I am used this method everytime but i can't explain in detail like you. That's 🔥