I'm pretty new to reactJS, and may have either over complicated things or didn't plan it properly.
I have encountered an issue with using the useState hook to keep track of properties in my project.
ok, so here goes
value
is taken from an input text box as type text, due to using react-hook-form
type
is then checked, to see if it's equal to "Income" or "Expense"type == "income"
then just set the value
using setValue
without any changes, else if type == "expense"
then *=-1
and set value
using setValue
parseInt()
is used with the setValue
to make sure the value is saved as a numbersetRecords
is called to add the relevant information into record
array of objects.so as you can see from the 5 steps above, useState hook setValue
and setRecords
is getting called one after the other, the issue I'm having at the moment is that on the initial run of the app, the value
would append instead of adding.
from what I understand is that because the useState hook is asynchronous and does not return a result immediately, so by the time the setRecords
is called, value
still of type text instead of number
I have been searching online for a while but was unable to find a good way to resolve this issue, without using useEffect hook, I have tried to use useEffect hook, but I didn't manage to get it to work, the user is allowed to enter the same value
and the app should still append the value into the record
array of objects.
in the end, I decided to create a new property currentValue
and set it on form submit, because this is not a state and will update immediately, it won't have to wait for setValue
to update the value
state.
Is there a better way to work around this issue? or is the workaround ok?
import React, { useEffect, useContext } from "react";
import RecordList from "./RecordList";
import { useForm } from "react-hook-form";
import { BudgetPlannerContext } from "../context/BudgetPlannerContext";
const BudgetPlanner = () => {
const { register, handleSubmit, errors } = useForm();
const [
records,
setRecords,
total,
setTotal,
type,
setType,
value,
setValue,
description,
setDescription,
] = useContext(BudgetPlannerContext);
let currentValue = 0;
useEffect(() => {
setTotal(() =>
records.reduce((acc, curr) => {
return acc + curr.value;
}, 0)
);
}, [records, setTotal]);
const onFormSubmit = (data) => {
if ((type === "Income") & (data.value !== 0)) {
currentValue = parseInt(data.value);
setValue(parseInt(data.value));
} else if ((type === "Expense") & (data.value !== 0)) {
currentValue = parseInt((data.value *= -1));
setValue(parseInt((data.value *= -1)));
} else {
return null;
}
setDescription(data.description);
processValue(type, currentValue);
};
const processValue = (type, currentValue) => {
updateRecord(type, currentValue);
//setValue(0);
//setDescription("");
//console.log(records);
};
const updateRecord = (type, currentValue) => {
setRecords((oldRecord) => [
...oldRecord,
{
type: type,
value: currentValue,
description: description,
id: Math.random() * 1000,
},
]);
};
return (
<div className="ui segment">
<div className="search-bar ui segment">
<form className="ui form" onSubmit={handleSubmit(onFormSubmit)}>
<div className="field">
<select
onChange={(e) => setType(e.target.value)}
name="todos"
ref={register}
className="filter-todo"
>
<option value="" defaultValue="defaultValue">
---
</option>
<option value="Income">Income</option>
<option value="Expense">Expense</option>
</select>
<input
name="description"
type="text"
placeholder="Description"
ref={register}
/>
<input
name="value"
type="number"
placeholder="Value"
ref={register}
/>
</div>
<div>Total: {total}</div>
<button type="submit">Submit</button>
</form>
</div>
<RecordList records={records} setRecords={setRecords} />
</div>
);
};
export default BudgetPlanner;
If I were you, I'd just pass the new value as a parameter to the other functions you're calling:
const onFormSubmit = (data) => {
if (data.value === 0 || (type !== 'Income' && type !== 'Expense')) {
return;
}
const newValue = parseInt(data.value) * (type === 'Income' ? 1 : -1);
setValue(newValue);
setDescription(data.description);
processValue(type, newValue);
};
No need for an extra outer variable.
Another option is to use useEffect
(or, preferably, useLayoutEffect
) and call processValue
when value
changes:
const onFormSubmit = (data) => {
if (data.value === 0 || (type !== 'Income' && type !== 'Expense')) {
return;
}
const newValue = parseInt(data.value) * (type === 'Income' ? 1 : -1);
setValue(newValue);
setDescription(data.description);
};
useLayoutEffect(() => {
if (value) {
processValue(type);
}
}, [value]);
and have processValue
use the outer value
stateful variable.