0

I think I'm not understanding something fundamental about React hooks or maybe even just React in general. I'm setting values from the backend.

const [values, setValues] = useState({
      rentAmount: props.values.zestimates.rent_zestimate.toLocaleString('en-US'),
      monthlyPayment: props.values.zestimates.rent_zestimate.toLocaleString('en-US'),
      vacancyAmount: (props.values.zestimates.rent_zestimate * 0.1).toLocaleString('en-US'),
      vacancyRate: 10,
      repairsAmount: (props.values.zestimates.rent_zestimate * 0.1).toLocaleString('en-US'),
      repairsRate: 10,
      propertyManagementAmount: (props.values.zestimates.rent_zestimate * 0.1).toLocaleString('en-US'),
      propertyManagementRate: 10,
    })

The user can change rentAmount from a textbox as well as change the rates from other textboxes and what I want is for 3 different values to update once rentAmount has changed but I'm running into an issue where the last declared setValues() is the only one to run.

I have 5 useEffects set up (probably not the way to do this but kind of stumbled across React Hooks halfway through this hobby project) but I'm focusing on just 3 for this question

  useEffect(() => {
    setValues({ ...values, vacancyAmount: formatter.format(values.rentAmount.toString().replace(/,/g, '') * values.vacancyRate/100).replace('$', '')})
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [values.vacancyRate, values.rentAmount])

  useEffect(() => {
    setValues({ ...values, repairsAmount: formatter.format(values.rentAmount.toString().replace(/,/g, '') * values.repairsRate/100).replace('$', '')})
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [values.repairsRate, values.rentAmount])

  useEffect(() => {
    setValues({ ...values, propertyManagementAmount: formatter.format(values.rentAmount.toString().replace(/,/g, '') * values.propertyManagementRate/100).replace('$', '')})
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [values.propertyManagementRate, values.rentAmount])

My issue is, the last setValues for propertyManagementAmount runs just fine when rentAmount is updated but the other 2 don't run and I don't understand why.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Codeman27
  • 13
  • 4
  • might be because the value is overridden by the last useEffect. Try using the callback version of useEffect. e.g. `setValues((value) => ({ ...values, repairsAmount: formatter.format(values.rentAmount.toString().replace(/,/g, '') * values.repairsRate/100).replace('$', '')}))` – kkesley Jun 27 '20 at 02:46

2 Answers2

2

Yep... this is slightly confusing.

When you run setValues the values property is what it was on the last render. So if you did this:

const [value, setValue] = useState(1);

useEffect(() => {
  setValue(value + 1);
  setValue(value + 1);
  setValue(value + 1);
}, []);

value will be 2 after the first render. Because on each setValue execution value is 1 from the last render. Also bonus, you should include value as a dependency to useEffect which will cause an infinite re-render (because value is being changed by the useEffect and value also causes it to run)... the solution to your problem also fixes this.

If you did this:

const [value, setValue] = useState(1);

useEffect(() => {
  setValue(value => value + 1);
  setValue(value => value + 1);
  setValue(value => value + 1);
}, []);

Then value will be 4, as the function form of useState gets the last value set. Also, value does not need to be listed as a dependency of useEffect which solves the issue mentioned above.

So just change your:

setValues({ ...values, vacancyAmount...

to

setValues(values => { ...values, vacancyAmount...

Documentation on this is here.

enter image description here

Diesel
  • 5,099
  • 7
  • 43
  • 81
  • Thanks for your help, with your answer and kkesley's comment I got it sorted. I did have to use parenthesis like kkesley's comment suggested. Also, would give ya an upvote but still don't have enough reputation. – Codeman27 Jun 27 '20 at 03:06
  • Mark the answer as accepted if it is correct please, glad it worked! I hope you understand why the function form works and the value form doesn't. Ask if you don't. – Diesel Jun 27 '20 at 03:09
0

You can set all of them in one useEffect like this.

useEffect(() => {
    setValues(prevValues => ({
      ...prevValues,
      vacancyAmount: formatter
        .format(
          (prevValues.rentAmount.toString().replace(/,/g, "") *
            prevValues.vacancyRate) /
            100
        )
        .replace("$", ""),
      repairsAmount: formatter
        .format(
          (prevValues.rentAmount.toString().replace(/,/g, "") *
            prevValues.repairsRate) /
            100
        )
        .replace("$", ""),
      propertyManagementAmount: formatter
        .format(
          (prevValues.rentAmount.toString().replace(/,/g, "") *
            prevValues.propertyManagementRate) /
            100
        )
        .replace("$", "")
    }));
  },[values.rentAmount]);
Tony Nguyen
  • 3,298
  • 11
  • 19