Turning off a loading spinner has to happen on both the success and the failure path, so it got duplicated into then and catch, and one of the two eventually drifted.
// before
fetch(url)
.then(r => { setLoading(false); return r.json(); })
.catch(e => { setLoading(false); report(e); });
// ES2018
fetch(url)
.then(r => r.json())
.catch(report)
.finally(() => setLoading(false));
The callback receives no arguments, deliberately — it is for cleanup, not for inspecting the outcome. It also passes the value or rejection through unchanged unless it throws itself, so it can be inserted anywhere in a chain without altering what comes after. That transparency is the property that makes it composable, and it is the reason the hand-written version was usually subtly wrong.