I usually like to wrap React useEffect
calls inside other functions, such as useOnMount
, useOnChange
, useOnceTrue
, etc. I've often had pushback on that, and the argument is usually "it's another abstraction layer to learn", which is a fair point. I thought I'd summarize why I think this layer of abstraction is beneficial.
One of the reasons why we use Array methods like .filter
, .map
or .reduce
instead of using a generic for (...)
loop despite their lesser performance is that they provide a higher level of expressiveness, and they make the underlying logical operation much more semantically apparent.
For example, the two following snippets perform the same operation, but the second one makes it abundantly clear with just a glance that there is a filtering operation happening.
const newItems = []
for (let i = 0; i < items.length; i++) {
if (items[i] < 0)
newItems.push(items[i])
}
const newItems = items.filter(i => i < 0)
Similarly, using a hook derived from useEffect
allows to bring a level of semantic clarity to the operation. The next two snippet both perform the same logic, but one of them allows me to know at a glance what is going on.
useEffect(() => {
if (teamId !== undefined) {
setLoaded(true)
}
}, [teamId !== undefined])
useOnceTrue(teamId !== undefined, () => setLoaded(true))
Another reason I do it is because I'm annoyed at the order of arguments for useEffect
. The logical notation for effects should be cause -> effect
! For some reason React decided to have effect
then cause
, and to make matters worst the effect
is more often than not an inline closure, which means the cause
can be separated by many lines from it's effect :(
// code as we read it with other constructs:
if (cause) {
// effect
// many lines of code
}
// code as react makes us write it:
useEffect(/* effect */ () => {
// many lines of code
}, [cause])
The natural order is "if this, then that"
or "when this changes, do that"
. As intended by God.
Even worst, most code editors will syntax-fold function calls as something like this:
useEffect(() => {...})
Which means the only way to find which effect one is looking for is to unfold it.
I'll admit that these readability benefits might seem marginal for small components, but they do really shine in more complex cases where there are many different effects inside a component. I hope this explains better why I've been doing it this way.