Stop using useEffect hook to initialize your React components

If I have some logic in a react component that needs to run only when my component renders for the first time, I often use a useEffect hook with an empty dependency array to achieve that, something like below example where the initial value of "count" is set in a useEffect hook.

import React, {useState, useEffect} from 'react';

export const Counter = (props) => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Initialization code runs only once!');
    const initialValue = () => {
      // some complex logic here that calculates the value
      return 135; // just for sake of demo
    }
    setCount(initialValue);
  }, [])

  console.log('Component rendered');

  return (
    <div>
      <p>{count}</p>
      <button onClick={
        () => { setCount(count+1); }
      }>Counter</button>
    </div>
  );
}

But useEffect according to React's own documentation should be used as the last resort for such kind of stuff and I personally find a code easier to understand and debug when it relies less on magic hooks. Here for example we can completely avoid using the useEffect hook by putting initialization code in a function and reference the function in our component's useState:

import React, {useState} from 'react';

const Counter = (props) => {
  const [count, setCount] = useState(getInitialValue);
  
  function getInitialValue() {
    console.log('Initialization code runs only once!');
    // some complex logic here that calculates the value
    return 135; // just for sake of demo
  }
  
  console.log('Component rendered');
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={
        () => { setCount(count+1); }
      }>Counter</button>
    </div>
  );
}

Note that we wrote useState(getInitialValue) and not useState(getInitialValue())! Even though the latter works too, but then React will execute the function on every render which could reduce performance of our app or cause bugs that are hard to debug. Another way is to use an anonymous function like this: useState(() => {getInitialValue()}), but I personally find useState(getInitialValue) easier to read.

Note that if your initial calculations are async then you would still need to use useEffect since async functions can not be passed to useState as initial value.

  useEffect(() => {
    async function getInitialValue() {
      const initialValue = await fetch('/api/count');
      setCount(initialValue)
    }
    getInitialValue();
  }, []);