How do you correctly store value from a promise function without delay ?
I am trying using a useEffect hook, but still my state is delayed.
It is problematic as if the user is validating his cart, a wrong tax may be applied.
useEffect(() => {
SalesTax.getSalesTax(country, region).then((tax) => {
setTax(tax.rate);
});
}, [region]);
<RegionDropdown
country={country}
className="capitalize input"
value={region}
onChange={(val) => {
setRegion(val);
}}
required
countryValueType="short"
valueType="short"
/>
You should do something like this:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
SalesTax.getSalesTax(country, region).then((tax) => {
setTax(tax.rate);
setLoading(false);
});
}, [region]);
if (loading) {
return <p>Loading...<p/>;
}
This is a normal way of handling asynchronous actions in React.
You'll be using a lot of asynchronous values so how about making a hook such as useAsync?
function useAsync(f, [_0, _1, _2, _3, _4]) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
setLoading(true)
Promise.resolve(f(_0, _1, _2, _3, _4))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, [f, _0, _1, _2, _3, _4])
return {loading, error, result}
}
Now use your hook in your component -
function MyComponent({country, region}) {
const {loading, error, result} =
useAsync(SalesTax.getSalesTax, [country, region])
if (loading)
return <p>Loading...</p>
else if (error)
return <p>Error: {error.message}</p>
else
/* result is usable here */
return <RegionDropdown .../>
}
_0, _1, _2, ... are required so ESLint can statically analyze the dependencies. In a future version, perhaps an array can be used. Another option is to disable ESLint for this hook, but note this is discouraged by Dan Abramov of React -
function useAsync(f, args) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
setLoading(true)
Promise.resolve(f(...args))
.then(setResult, setError)
.finally(_ => setLoading(false))
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[f, ...args])
return {loading, error, result}
}