Constructors in Functional Components With Hooks
Tags: react, hooks, javascript, tutorial
Published: 2020-04-05 18:38:58

[**NOTE:** I've since written an update to this article with an improved solution.  It can be read here: https://dev.to/bytebodger/streamlining-constructors-in-functional-react-components-8pe]

When you're building functional components in React, there's a little feature from class-based components that simply has no out-of-the-box equivalent in functions.  This feature is called a _constructor_.

In class-based components, we often see code that uses a constructor to initialize state, like this:

```javascript
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

  render = () => {
    return (
      <button
        onClick={() =>
          this.setState(prevState => {
            return { counter: prevState.counter + 1 };
          })
        }
      >
        Increment: {this.state.counter}
      </button>
    );
  };
}
```

Honestly, I've come to view code like this as silly and unnecessarily verbose.  Because even in the realm of class-based components, the exact same thing can be done like this:

```javascript
class App extends Component {
  state = { counter: 0 };

  render = () => {
    return (
      <button
        onClick={() =>
          this.setState(prevState => {
            return { counter: prevState.counter + 1 };
          })
        }
      >
        Increment: {this.state.counter}
      </button>
    );
  };
}
```

As you see, there's no need for a constructor simply to initialize your state variables, unless you have to initialize the state variables _based upon the props_.  If this isn't necessary, you can declare initial state directly inside the class.

## Constructors... for Functions?

If we transition into the functional/Hooks side of things, it would seem that the Hooks team had the same idea.  Because when you look at the FAQ for Hooks, it has a section dedicated to answering, "How do lifecycle methods correspond to Hooks?"  The first bullet point in this section says:

> `constructor`: Function components don’t **_need_** (emphasis: _mine_) a constructor. You can initialize the state in the `useState` call. If computing the initial state is expensive, you can pass a function to `useState`.

Wow...

I don't know if this "answer" is ignorant.  Or arrogant.  Or both.  But it doesn't surprise me.  It's similar to some of the other documentation I've seen around Hooks that makes all sorts of misguided assumptions _for you_.

This "answer" is ignorant because it assumes that the _only_ reason for a constructor is to initialize state.  

This "answer" is arrogant because, based on its faulty assumptions, it boldly states that you don't _need_ a constructor.  It's like going to the dentist for a toothache - but the dentist doesn't fix the problem.  He just pats you on the head and says, "There, there.  You don't really _need_ that tooth.  Now run along..."

The massive oversimplification in their dismissive FAQ overlooks the basic fact that there are other, perfectly-valid use-cases for a constructor (or, constructor-like functionality) that have nothing to do with initializing state variables.  Specifically, when I think of a constructor, I think of these characteristics.

1. Code that runs _before anything else in the life-cycle of this component_.

1. Code that runs once, _and only once_, for the entire life-cycle of this component.

To be clear, is a constructor _usually_ needed in _most_ components?  No.  Certainly not.  In fact, I'd say that the need for constructor-type logic is the exception, not the rule.  Nevertheless, there are certain times when I absolutely need logic to run _before anything else in the life-cycle of this component_, and I absolutely need to ensure that it will run once, _and only once_, for the entire life-cycle of this component.

So despite the Hooks team's bold assertions, the fact is that there _are_ times when I _do_ need a constructor (or some equivalent).

## The Challenge of Functional/Hooks Life-Cycles

The biggest "problem" with life-cycles in functions/Hooks is that... there are none.  A function doesn't have a life-cycle.  It just... _runs_.  Whenever you call it.  So from that perspective, it's understandable that there's no easy, out-of-the-box equivalent for a constructor in a functional component.

But despite the Holy Praise that JS fanboys heap upon the idea of _functional programming_, the simple fact is that a functional _component_ doesn't really "run" like a true function.  Sure, you may have that comforting `function` keyword at the top of your code (or, even better, the _arrow syntax_).  But once you've created a functional _component_ in React, you've handed over control of exactly _how_ and _when_ it gets called.

That's why I often find it incredibly useful to know that I can create some bit of logic that will run once, _and only once_, before any other processing takes place in the component.  But when we're talking about React functional components, how exactly do we do that?  Or, more to the point, where do we put that logic so it doesn't get called repeatedly on each render?

## Tracing the "Life-Cycle" of Functions/Hooks

(**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-hook)

This will best be illustrated with some examples.  So let's first look at a dead-simple example of logic that runs _in the body_ of a function:

```javascript
const App = () => {
  const [counter, setCounter] = useState(0);

  console.log("Occurs EVERY time the component is invoked.");
  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```

This is the simplest illustration of a function's "life-cycle".  In a class-based component, we had the comfort (IMHO) of a `render()` function.  And if a particular bit of logic should _not_ run on _every_ re-render, the process was pretty straight-forward:  Just don't put that logic _in_ the `render()` function.

But functional components offer no out-of-the-box equivalent.  There _is no_ `render()` function.  There is only a `return`.  The `return` (and all the rest of the code in the body of the function) gets called _every single time this function is called_.  

I will freely raise my hand and admit that this threw me for a loop when I first started writing functional components.  I would put some bit of logic _above_ the `return`, and then I'd be surprised/annoyed when I realized it was running _every single time the function was called_.  

In hindsight, there's nothing surprising about this at all.  The `return` is _not_ analogous to a `render()` function.  To put it in different terms, the entire function _is_ the equivalent of the `render()` function.

So let's look at some of the other Hooks that are available to us out-of-the-box.  First, I spent time playing with `useEffect()`.  This leads to the following example:

```javascript
const App = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    console.log(
      "Occurs ONCE, AFTER the initial render."
    );
  }, []);

  console.log("Occurs EVERY time the component is invoked.");
  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```
This gets us a _little_ closer to our goal.  Specifically, it satisfies my second condition for a constructor.  It is run once, _and only once_, for the entire life-cycle of this component.

The problem is that it still runs _after_ the component is rendered.  This is completely consistent with the Hooks documentation, because there it states that:

> By default, effects run _after_ (emphasis: _mine_) every completed render.

I also played around with `useLayoutEffect()`, which leads to this example:

```javascript
const App = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    console.log(
      "Occurs ONCE, AFTER the initial render."
    );
  }, []);

  useLayoutEffect(() => {
    console.log(
      "Occurs ONCE, but it still occurs AFTER the initial render."
    );
  }, []);

  console.log("Occurs EVERY time the component is invoked.");
  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```
`useLayoutEffect()` gets us no closer to having a true "constructor".  `useLayoutEffect()` fires _before_ `useEffect()`, but it still fires _after_ the render cycle.  To be fair, this is still completely consistent with the Hooks documentation, because `useLayoutEffect()` is still... an _effect_.  And effects always fire _after_ rendering.

So if we want something that truly approximates the functionality of a constructor, we'll need to manually control the firing of that function.  Thankfully, this is totally in our control, if we're willing to manually crank out the code that's needed to support it.  That would look like this:

```javascript
const App = () => {
  const [counter, setCounter] = useState(0);
  const [constructorHasRun, setConstructorHasRun] = useState(false);

  useEffect(() => {
    console.log(
      "Occurs ONCE, AFTER the initial render."
    );
  }, []);

  useLayoutEffect(() => {
    console.log(
      "Occurs ONCE, but it still occurs AFTER the initial render."
    );
  }, []);

  const constructor = () => {
    if (constructorHasRun) return;
    console.log("Inline constructor()");
    setConstructorHasRun(true);
  };

  constructor();
  console.log("Occurs EVERY time the component is invoked.");
  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```
This gets us a lot closer to the stated goals.  The manual `constructor()` function runs once, _and only once_, for the "life-cycle" of this function.  It accomplishes this goal by leveraging a manual state variable - `constructorHasRun` - and refusing to re-run the `constructor()` functionality if that variable has been flipped to `true`.

This... "works".  But it feels very... _manual_.  If you require constructor-like features in your functional components, then under this approach, you'd have to _manually_ add the tracking variable to the state of every component in which it's used.  Then you'd need to ensure that your `constructor()` function is properly set up to only run its logic based on the value in that state variable.  

Again, this "works".  But it doesn't feel particularly satisfying.  Hooks are supposed to make our life _easier_.  If I have to _manually_ code this functionality in every component where I need constructor-like features, then it makes me wonder why I'm using functions/Hooks in the first place.

## Custom Hooks to the Rescue

This is where we can leverage a custom Hook to standardize this process.  By exporting this into a custom Hook, we can get much closer to having a "true" constructor-like feature.  That code looks like this:

```javascript
const useConstructor(callBack = () => {}) => {
  const [hasBeenCalled, setHasBeenCalled] = useState(false);
  if (hasBeenCalled) return;
  callBack();
  setHasBeenCalled(true);
}

const App = () => {
  useConstructor(() => {
    console.log(
      "Occurs ONCE, BEFORE the initial render."
    );
  });
  const [counter, setCounter] = useState(0);
  const [constructorHasRun, setConstructorHasRun] = useState(false);

  useEffect(() => {
    console.log(
      "Occurs ONCE, but it occurs AFTER the initial render."
    );
  }, []);

  useLayoutEffect(() => {
    console.log(
      "Occurs ONCE, but it still occurs AFTER the initial render."
    );
  }, []);

  const constructor = () => {
    if (constructorHasRun) return;
    console.log("Inline constructor()");
    setConstructorHasRun(true);
  };

  constructor();
  console.log("Occurs EVERY time the component is invoked.");
  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```

If you want to see it without the failed attempts to use `useEffect()` and `useLayoutEffect()`, and without the manual implementation of `constructor()`, it looks like this:

```javascript
const useConstructor(callBack = () => {}) => {
  const [hasBeenCalled, setHasBeenCalled] = useState(false);
  if (hasBeenCalled) return;
  callBack();
  setHasBeenCalled(true);
}

const App = () => {
  useConstructor(() => {
    console.log(
      "Occurs ONCE, BEFORE the initial render."
    );
  });
  const [counter, setCounter] = useState(0);

  return (
    <>
      <div>Counter: {counter}</div>
      <div style={{ marginTop: 20 }}>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
    </>
  );
};
```

By leveraging a custom Hook, we can now `import` the "constructor-like" functionality into any functional component where it's needed.  This gets us, umm... 99% of the way there.

Why do I say that it's only 99% effective??  It satisfies both of my conditions for a "constructor".  But... it only accomplishes this goal, in the example shown above, because _I invoked it at the very top of the function_.  

There's still nothing stopping me from putting 100 lines of logic _above_ the `useConstructor()` call.  If I did that, it would fail my original requirement that the logic is run _before anything else in the life-cycle of this component_.  Still... it's a fairly decent approximation of a "constructor" - even if that functionality is dependent upon _where_ I place the call in the function body.

For this reason, it might be more intuitive to rename `useConstructor()` to `useSingleton()`.  Because that's what it does.  It ensures that a given block of code is run once, _and only once_.  If you then place that logic at the very top of your function declaration, it is, effectively, a "constructor", for all intents and purposes.