'Trying to run function only if state changes

I want to run the set totals function only if the hour's state has changed. It is running every time the component mounts instead of only if the value changes. The this.state is apart of a context file that is extremely large so I only pasted the function being used

context.js (Class Component)
set Total
        if (this.state.hours > 0) {
               this.setState((prevState) => {
                 if (prevState.hours !== this.state.hours) {
                   console.log(prevState.hours);
                 }
                 return {
                   total: this.state.total + this.state.hours * ratePerHour * Math.PI,
                 };
               });
              console.log(this.state.total, '+', this.state.hours, '*', ratePerHour);
            }
    This is my component tha
     import React, { useState, useEffect, useContext,useRef } from 'react';
    import { ProductContext } from '../pages/oniContext';
    import { Container,Badge } from 'reactstrap';
    import {
      Subtitle,
      Description,
      Titlespan2,
    } from '../components/common/title/index';
    import { total } from '../components/total';
    export const FinalQuote = () => {
      const pCR = useContext(ProductContext);
        const prevCountRef = useRef();
    
    
        useEffect(() => {
          alert('Run')
          console.log(pCR.hours, 'Final Quote Run', pCR.total);
          pCR.setTotal();
          console.error(pCR.hours);
      }, [pCR.hours]);
    
      return (
        <section className="testimonial-wrapper gradient-color" id="testimonial">
          <Container>
            <div className="main-title-wrapper">
              <Subtitle Class="site-subtitle gradient-color" Name="Your Quote" />
              <Titlespan2
                Class="sitemain-subtitle"
                Name={`$${Math.round(pCR.total)}`}
              />
              <Description
                Class="site-dec"
                Name="The Shown Price is only an estimate and may increase or decrease based on demand and extent of work"
              />
              {pCR.activeAddOns.map((service, index) => (
                <Badge color="info" pill>
                  {service.title}
                </Badge>
              ))}
            </div>
          </Container>
        </section>
      );
    };


Solution 1:[1]

You can achieve this by using componentDidUpdate life cycle function in your component class. As per the docs

componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.

Means, whenever the state of the component will change, the componentDidUpdate code block will be called. So we can place an if condition in the block to compare the new state with the previous state, can calculate total and recommit it to the state. Code ?

class MyComponent extends React.Component {
  
  constructor() {
      super();

      this.state = {
          hours: 0,
          total: 0,
          ratePerHour: 10
      };
  }

  componentDidUpdate(prevProps, prevState) {
      if (prevState.hours !== this.state.hours) {
         // Calculate total
         this.setState({
             total: this.state.total + this.state.hours * this.state.ratePerHour * Math.PI
         }
      }
  }

  render() {
    return <AnotherComponent />;
  }
}

Plus it is important to note that (ref: docs)

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.

In case of any other query, please feel free to reach out in the comments.

Solution 2:[2]

It's been a minute since I've worked with newer React features but when I can I use useEffect in my functional components. The second parameter is the the variable you want to watch for changes. If you don't supply a second parameter it'll run similar to componentDidMount and componentDidUpdate. An example of possible use:

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

function Example() {
  const [count, setCount] = useState(0);
  const [test, setTest] = useState('');

  // Specify to watch count because we have more than one state variable
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Here's some of their documentation: https://reactjs.org/docs/hooks-effect.html

Solution 3:[3]

In my case, even when I added the second argument to useEffect which is an array of dependencies. It is running every time the component mounts instead of only if the value changes and I guess this is because I initialized my state variable like so

const[myStateVariable, setMyStateVariable] = React.useState('');

so I had to make this condition in my useEffect function

React.useEffect(() => {
    if(myStateVariable !==''){
      getMyData()
    }
  }, [myStateVariable]);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Moiz Husnain
Solution 2 Dillan Wilding
Solution 3 hakima maarouf