Search code examples
javascriptreactjsuse-effectuse-state

Consequences of using computed variables vs useState/useEffect


If I have a variable whose value can be fully derived based on the value of another property, is there any consequence/pitfall to initializing a computed variable vs using a combination of useState/useEffect to track the variable? Let me illustrate with a contrived example:

/**
 * ex paymentAmounts: [100, 300, 400]
 */
const Option1 = ({paymentAmounts}) => {
  const [average, setAverage] = useState(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)

  useEffect(() => {
    setAverage(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
  }, [paymentAmounts])

  return (
    <div>
      Average: {average}
    </div>
  )
}

or more simply

/**
 * ex paymentAmounts: [100, 300, 400]
 */
const Option2 = ({paymentAmounts}) => {
  const average = paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length

  return (
    <div>
      Average: {average}
    </div>
  )
}

Am I giving up any control and/or React benefits by using Option2?

Vue.js seems to have this option via computed properties.


Solution

  • You use state (as in useState that is) when you want to track something in your component (or share it between its children) between rerenders. In your case, you're getting this "state" from your parent component (the parent is the actual state holder). Every change in the parent state (paymentAmounts in your situation) will reflect in your child component automatically.

    As a general "rule", don't use state for data that can be calculated. Keep in mind that every change of a state tracked variable will force the component to re-render. Another bad usage example is this:

    const Example = ({variable1}) => {
      const [variable, setVariable] = useState(variable1);
    
      ...
    }
    

    A couple of additional notes:

    1. In your first solution you introduce an additional overhead by using useEffect. As pointed out already, your child component will always re-render and recalculate the average when your parent's state change.
    2. You're using setAverage wrong in your useEffect hook. The setter takes either a new value or a function that accepts the current one and returns the new one.
    3. Not sure what you expect from the paymentAmounts / paymentAmounts.length calculation. I suppose it is just a dummy code, but if not, look into it. You're dividing the array itself (not the sum of its values) by its length.

    So, in short - Yes, you should be using a simple variable to calculate the average and ditch the useState/useEffect. Not only you're NOT giving up any benefits, you're actually making your code more performant, maintainable, readable and error-free.