DEV Community

Cover image for Constructors in Functional Components With Hooks

Constructors in Functional Components With Hooks

Adam Nathaniel Davis on April 05, 2020

[NOTE: I've since written an update to this article with an improved solution. It can be read here: https://dev.to/bytebodger/streamlining-constru...
Collapse
 
jz222 profile image
Timo Zimmermann • Edited

In this case, I would use useRef instead of useState to omit an unnecessary render cycle, even though it's a small one.

const useConstructor = (callBack = () => {}) => {
  const hasBeenCalled = useRef(false);
  if (hasBeenCalled.current) return;
  callBack();
  hasBeenCalled.current = true;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Good point! I haven't really messed around enough with useRef(). This works better.

Collapse
 
molinch profile image
Fabien Molinet • Edited

Hey @bytebodger I am new to hooks, I previously only used components where I used to rely on the constructor. I wonder if I'm doing it right... since I really miss the constructor functionality, I created two hooks: useCreateOnce (returns a value) and useRunOnce.

But now I'm using these 2 in many places so I wonder if there isn't another way, which follows more React hooks "intended" philosophy? Or maybe I shouldn't worry that much?

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis • Edited

dev.to/spukas/4-ways-to-useeffect-pf6

This article right here on Dev.to is pretty handy - so much so that I stuck it in my reading list. A constructor is ultimately a lifecycle method. And in Hooks, there is exactly one lifecycle method - useEffect(). As the name implies, there is no such thing as a useEffect() call that runs before the component renders (since, an "effect" has to happen... after something else). And as the article above implies, useEffect() can actually be used four different ways - although I don't personally believe that any of them are intuitive just by reading the names/arguments.

Collapse
 
wolverineks profile image
Kevin Sullivan

would lazy initialization of useState work as a useSingleton
e.g.

const useSingleton = (initializer) => {
  React.useState(initializer)
}

const Foo = () => {
  useSingleton(() => {
    // only runs once
  })
  ...
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
peerreynders profile image
peerreynders • Edited

A more fleshed out example:

// custom hook "use initializer"
function useInit(init, ...args) {
  const tuple = useState(null);
  const [state, setState] = tuple;

  if(state !== null) {
    return tuple;
  }

  const initState = init(setState, ...args);
  setState(initState);
  return [initState, setState];
}

// Component Parts
function initCounter(setState, startAt) {
  const increment = state => ({...state, count: state.count + 1});
  const clickHandler = () => setState(increment);

  return {
    count: Number(startAt),
    clickHandler
  };
}

// Component
function Counter({startAt}) {
  const [{count, clickHandler}, setState] = useInit(initCounter, startAt);

  return html`
    <p>
      <button onClick=${clickHandler}>Click me</button>
    </p>
    <output>You clicked ${count} times</output>
  `;
}

function App() {
  return html`<${Counter} startAt="12" />`;
}
Enter fullscreen mode Exit fullscreen mode

Gist

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Yep. That works as well!

Collapse
 
isaachagoel profile image
Isaac Hagoel

For me the fact that all of the hooks are invoked every time and have to bail out if they don't need to run (basically by using an "if" statement explicitly like you've demonstrated or implicitly using dependency arrays) is hugely offensive. So much unneeded work and wasted electricity plus wasted brain cycles (should it run every time or not...)
The virtual Dom is already the king of wasted CPU cycles (diffing all of the things when barely anything has changed) but at least it doesn't put a constant mental tax on the developer. Until you need to start manually preventing it from updating every time that is.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I think you know this already, but I couldn't agree with you more. One (perfectly valid) response to this entire post would be, "Dooood, if you're so in love with constructors, then just write class-based components." But as (I assume) you've experienced already, sometimes you find yourself on a project / assignment / whatever where Pattern X is already the "accepted norm". And you're thinking, "Well... I usually use Pattern Y, which has Feature A. But, I don't see any analog for Feature A in this new pattern."

For the first time since they were introduced in Oct 18, I find myself on a team where we are (and plan to continue) cranking out functional components - with lots-and-lots of Hooks. So, in that environment, I'm trying to figure out the best way to adapt to "Pattern X" (in this case, Hooks) without losing some of the key features that I enjoyed in "Pattern Y" (in this case, class-based components).

I'm also trying, oh-so-hard, to not be that guy. You know that guy. He's the one who's always done things a "certain way", and when the team says, "No, we're gonna do this new project in a different way," he sits in the corner and pouts cuz he really just wants to do everything the same way he's always done it in the past.

Collapse
 
wolverineks profile image
Kevin Sullivan

Could you give an example where a constructor would be useful?

Collapse
 
alexvukov profile image
alex-vukov

Unfortunately using a constructor seems to be the only way to go if you are using Redux and you don't want to blink a "no data" message or stale data before a fetch action is dispatched in useEffect. Having worked with other frameworks, I am simply amazed by having to do this ugliness in React...

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Interesting/related note: This article has quietly gathered more views than anything else I've written. Unlike most of my articles, that have a certain "splash" for a few days - and then are rarely read again - this article continues to get a steady stream of traffic.

What's the point?? Well, it would seem that there are a lot of other people out there who are switching to Hooks, then saying, "Wait a minute. How do I create a constructor with Hook??", then they start googling and end up on my article. (And there seems to be very few other articles out there on the same topic.)

So while it may indeed be "ugliness", I surmise that there are many others who are confused/annoyed by the (FALSE) idea that you can simply use useEffect() to replace all of the previous lifecycle methods, and there's no logical reason to use the constructor functionality that we had in class-based components.

Thread Thread
 
alexvukov profile image
alex-vukov

Thank you for writing this article! I thought I was missing something by always having to skip renders with a flag which is set only after a fetch is started in useEffect or trying to do fetch calls in something like your constructor. Seems that React devs have missed the point that sometimes you need to have a cause before you can handle the effect...

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Having spent years working with the lifecycle methods that are available in class-based components, I'll be the first to tell you that they can, at times, cause major headaches. But the Hooks team seems to have decided that the way to cure the headache is to cut off the head.

Collapse
 
craigstowers profile image
Craig Stowers

I'm always willing to learn different ways and adapt, but i used to use a react constructor to pre-prep a lot of data, even for static websites. For instance i might translate a linear array into a 2d one better suited for defining a layout. Things like that. I guess i could do it another way but they all just seem like added complexity.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I will add here that, even in the documentation for class-based components, there's a general assumption on the part of the React team that constructor functionality is limited to initializing state. In my personal experience, I know that, on numerous occasions, I've found it prudent to handle other bits of logic in this part of the component life-cycle. Specifically, there are times when I want to prepare variables that will be used in the component, but I don't want the updating of those variables to be tied to state. In other words, there are sometimes some variables that I want to live outside of the rendering process. When I need to do any pre-processing on said variables, the constructor is an ideal place to do so.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

The easiest analogy is componentWillMount. Anything that anyone previously wanted to stuff in componentWillMount is a prime candidate to be put in the constructor. But don't take my word for it. This is directly from the React docs:

UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead for initializing state.

So if you were using old-skool React, you might've had a handful of components that used componentWillMount(). Then, that was deemed to be unsafe, and the maintainers themselves said, "Now you should move it to the constructor(). But with function-based components, there is no constructor.

Again, as I stated in the post, I'm not claiming that this is functionality that you'd need on all components. You won't even need it on most components. But the fact that we previously had componentWillMount() and constructor() signals that there are valid use-cases for it.

Collapse
 
jackmellis profile image
Jack • Edited

I often face the same issue but it is easily solved with a useRef. I usually write a useEagerEffect hook that runs before rendering, but still only runs when the deps array changes.

The part I really agree with, though, is how react's docs (and those of many other libraries) make these sweeping assumptions that nobody could ever possibly need anything outside of their perceived use cases. The blind arrogance you mention really frustrates me!

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

You're spot-on about the useRef. In fact, in my useConstructor NPM package, I wrote it with useRef, rather than the useState approached illustrated in the article. There was another commenter on this thread that was gracious enough to point me in that direction.

Collapse
 
devhammed profile image
Hammed Oyedele

I have used hooks in up to 3 production projects but I can't find the use cases of this hook honestly.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Haha - well, I'm not trying to convince you to use it. I'm just pointing out some ways that you could achieve it if you do find the use case.

I don't know if I've ever had the occasion to actually write my own closure. That doesn't mean that there aren't use-cases to write closures.

Collapse
 
devhammed profile image
Hammed Oyedele

Okay and funny enough, I use closures a lot of time e.g in factory functions.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

And that's why I said that I consider the Hooks documentation regarding constructors to be arrogant. It starts from the (false) assumption that there's only one reason to ever need/use a constructor (to initialize state) and then it doubles down on that folly by stating that there's simply no need for them anymore.

With closures, I'm sure there are many devs out there like me who aren't using them often (or at all) in their code. That doesn't mean that we should make a global statement that they're not needed. And we definitely shouldn't do anything to remove support for them.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

Ryan Carniato (SolidJS, MarkoJS) notes in: React Hooks: Has React Jumped the Shark? (Feb 2019):

The challenge here is, to use Hooks beyond simple scenarios requires you to be a competent JavaScript developer. And I don’t mean this in any way a slight. But if you are having issues with this or bind with Classes, Hooks are no simpler. Hooks force you to really understand closures, reference vs value types, and memoization, and more importantly have a keen eye (or linter) to make sure you do not miss any small piece required to make them work.

He has a number of other pro-Hook (but not fanboyish) articles:

That last one explains where hooks do make sense.

The fit with React at this point is somewhat awkward - one has to wonder whether React is maneuvering into a position where ditching the VDOM becomes an option (to replace it with modern Fine-Grained change detection).

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Oooh... these links look awesome. In particular, I love love LOVE the assessment that "Hooks are no simpler". I think this speaks to some of the frustration I've had with them, literally since the day they were announced. I'm not anti-Hooks. In fact, all of my current React dev is now Hooks-based. But I'm continually annoyed by some fanboys who act as though Hooks are self-evidently better, and everything else is clearly worse. They act as though the answer to, "Why Hooks?" is "Well... Hooks!!!"

The historical perspective in these articles is also awesome. I've made a few references in my previous articles to Knockout. It's amazing (to me) how many "senior" JS devs today don't even know what Knockout was/is. This author seems to have a very well-rounded approach to the whole thing.

Thread Thread
 
peerreynders profile image
peerreynders

I needed Deep dive: How do React hooks really work? (2019) before hooks made sense to me.

Collapse
 
craigstowers profile image
Craig Stowers • Edited

I appreciate this half rant, half instruction. Some of the workarounds to move to a hooks only react environment really are not all that clean or efficient.

Also just FYI: unless i'm mistaken and there is yet another way to declare functions, your const useConstructor variable is never initialized as a function.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

In both cases where I illustrated useConstructor, it was defined above the App function.

Collapse
 
craigstowers profile image
Craig Stowers • Edited

Yeah it's where you defined it that i don't quite understand and it throws errors for me. "Const declarations' require an initialization value."

Your one has:

const useConstructor(callBack = () => {}) {
Enter fullscreen mode Exit fullscreen mode

Should it not be this? (which works)

const useConstructor = (callBack = () => {}) => {
Enter fullscreen mode Exit fullscreen mode

I'm not trying to nitpick... i'm just uncertain of my own knowledge and the ever changing landscape of javascript.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Ahhh, yes. Good catch. I am missing the =>. Obviously, I didn't actually run these examples. I just typed them out. Thanks!

Collapse
 
krspv profile image
krspv

I had a problem related to this topic: I needed a unique-id in my component. In order to avoid generating it on every update I did this initially:

const [uniqId] = useState( generateUniqId() );
Enter fullscreen mode Exit fullscreen mode

However, that didn't help. The value of uniqId remained the same throughout all the updates, but the call to generateUniqId() was made on every update. I solved it like this:

const refUniqueId = useRef(null);
if (refUniqueId.current === null) {
  refUniqueId.current = generateUniqId();
}
Enter fullscreen mode Exit fullscreen mode

That made sure that generateUniqId() was called only once and not on every update.

Collapse
 
eliasmqz profile image
Anthony Marquez

I just wanna say this is a great article, thanks for this really helped me out with solving the lack of a constructor use case.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Thank you - I'm glad it helped!

Collapse
 
danieltkach profile image
Daniel Tkach

What about useMemo? It seems to work, what are the tradeoffs? Would it solve the constructor issue?

Collapse
 
ronakpanchal93 profile image
Ronak
Collapse
 
mdaltaftech profile image
Mohammed Altaf

Its very helpful Adam