
Making SetInterval Declarative with React Hooks - clessg
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
======
mharroun
Maybe I am crazy or old school but I though the concept of creating component
as a class with standard functions to override (and defining a contract with
props types) took a unwieldy language like JS and gave a sane and standard
approach to it.

I now look at more "modern" functional components /w hooks and find it VERY
hard to understand the data a component takes and how it uses/responses to
interactions.

It feels like react was class based... and now that functional coding is the
"in thing" its trying to shoehorn everything into functional components. In
the end it really turns me off to functional programming as I see many
developers sacrificing readable/understandable components to save a few lines
of code.

~~~
tokyodude
Agreed. The "everything needs to be a react component" is bad IMO. My example,
I'm making an image viewer. I want a context menu. IMO the ideal solution some
generic right-click (context menu) function/handler. you attach it to each
image. On right click the image is passed to the handler and the handler does
something with it. That's relatively low overhead. There's no per image data.

But the React stuff I found made a context menu of nested components per
image. Effectively
<image><contextmenu><menuitem/><menuitem/><menuitem/><menuitem/><menuitem/></contextmenu></image>.
For 1000 images that's 6 thousand components created, 6000 things added to the
virtual dom to be diffed, 11000 considering the content of each menuitem is
actually yet another dom node.

There's tons of react libraries like that where they try to shovel every
concept into a component.

~~~
mifreewil
> IMO the ideal solution some generic right-click (context menu)
> function/handler. you attach it to each image

Ideally, you would only have a single event listener that handles events for
all images.

~~~
frosted-flakes
With React, it's one and the same thing. React has a single event listener at
the top level, then creates synthetic events that it passes down the tree. You
can still do native event listeners, but there's no reason to, except for
listening for events on `window`, etc.

------
ryansolid
There is some difficulty in understanding how this works at first if you
haven't hit these problems before. But once you do, the payoff is immense.

You think of problems in terms of describing how data changes over time rather
than how am I going to make this display. The JSX part of the code becomes an
afterthought. In the same way you aren't thinking about what DOM mutations
React is actually doing behind the scenes, don't worry too much about how it
wires together just describe what it does. Counter is a wonderful example.
Look how contained and directed the hooks version is compared to jumbled mess
the class version is. Picture that class component was anymore complicated,
tracing through these lifecycle methods to figure out what is happening under
any number of different input changes. Where the hook tells you in its
definition when it updates. Each new behavior is just listed after and self-
contained. Consider how refactoring and breaking apart components works in
both scenarios.

This paradigm is not new. New to React but not at all new to Frontend JS UI
libraries. Ironically when presented in MobX, and KnockoutJS(way back in 2009)
it was considered simple. Somehow in 2019 with how far we have progressed,
it's too difficult?

~~~
warent
Calling the class component a "jumbled mess" is nothing short of hyperbole. It
could be simplified quite a bit however. The problem is an inappropriate use
of "setInterval" here. If the author used a recursive "setTimeout" function
instead, the entire "componentDidUpdate" function can be deleted.
Alternatively, keep the setInterval and all the logic in componentDidUpdate
could just be moved to the tick function.

This contrived example hasn't sold me on hooks at all.

------
jasonkillian
Wow, I really don't know what to think of this article. I have to give Dan
credit that every time I thought the code started looking really crazy, he'd
say something like: "Admittedly, [this] code can be disorienting. It’s mind-
bending to mix opposite paradigms." And I think it's a good sign he recognizes
the fair objections people will bring up.

In addition to the paradigm-mixing the article illustrates, I wonder if the
actual hook APIs make things more confusing than necessary at some points. For
example, in the `useEffect` hook API:

* the first argument is a function that on every render

* the return value of the first argument is a "cleanup" function which runs when the component is unmounted or before the effect is run again

* the third argument is an array of parameters which controls when the effect is run. If passed an empty array, the effect is only run at mount and cleaned up at unmount.

All the above means that the API is very implicit and doesn't self-document
its intent all that well. An example, from the article:

    
    
      useEffect(() => {
        function tick() {
          savedCallback.current();
        }
    
        let id = setInterval(tick, 1000);
        return () => clearInterval(id);
      }, []);
    

I'm sure with more use these hook patterns will become more familiar. But it's
clearly a lot harder to learn and understand than the original simplicity that
helped propel React to popularity.

Dan's arguments that hooks provide for easier abstraction and more
composability is interesting to me though - if hooks are going to be "worth
it" in the long run, I suspect this is why.

~~~
underwater
The pattern of returning an unregister function is a common one. But I agree
that the cache key array is strange.

I'd almost suggest that a seperate useEffectOnce function would be better, but
I'm sure the abstraction was heavily tested and bikeshedded before it was
released in this form.

~~~
feralfoo
You could create userEffectOnce as a custom hook:

function useEffectOnce(callback) { useEffect(() => { return callback() }, [])
}

------
Karupan
I do React at work and Elm for personal projects, and I feel the React
ecosystem is trying too hard to become something its not. Hooks to me are an
added level of complexity, and doesn't simplify the way I write or test
components. I don't really mind the class based components as I feel there is
less magic.

Personally, React had a great run, but it's time to move on to a more purpose
built language/framework.

------
daok
I read half the article and got lost. What I like about the "original" React
is that it is simple to understand, close to JavaScript in a sense that you do
not need to learn that much and be able to have a good result. I'm reading
more and more about Hooks and still have a hard time to have a clear idea of
how to avoid these pitfalls. At the point I am, it seems that Hooks has a lot
of hype from few big influencers but that the day-to-day developers will be
lost.

~~~
bromuro
Instead of spending hours reading, start playing with them, it is fun and in
few hours you will get why people are excited for hooks!

------
deadA1ias
Dan Abramov claims that React hooks haven't jumped the shark. All I'm seeing
in this article is exactly that.

~~~
nwienert
I’ve now converted a good chunk of a large stack over to them and the best way
to describe them is this: they are as big an improvement to React as React was
to its predecessors.

This example is meant to be a pathological case. The fact that you can build
complex interface logic and bundle it into a function that can then be
extended and composed is a huge leap forward.

I do think Hooks will take time for some of the best ones to become standard.
While I see some early “lodash for hooks” like libararies appearing, I could
also see a really nice PWA that let you search gists for popular hooks or
similar.

Hooks are a very nice solution to a very hard problem. But unless you’ve done
large scale frontend work and especially more complex interactive components
it may be hard to see. Although just getting rid of all the decorators/HOCs is
already a big win.

~~~
kaoD
> The fact that you can build complex interface logic and bundle it into a
> function that can then be extended and composed is a huge leap forward.

Unless you already used HOCs (in the Recompose sense).

Every single blog post is obsessed with comparing hooks with classes (I guess
because it's the only thing they improve upon). What about HOCs? I'm not sure
hooks are really that much of a leap forward compared to them.

HOCs only wart is their pollution of props which admittedly hooks solve (by
virtue of using local variables), but hooks have their own warts too as
evidenced in this article.

~~~
mlsarecmg
Hocs had several problems, they couldn't access context, re-using one result
in another is hard, they create a new component for every thing you want to
do, etc.

Take this example for instance:
[https://codesandbox.io/embed/26mjowzpr](https://codesandbox.io/embed/26mjowzpr)

It measures out the view, reads out media queries, uses the results to
construct a grid, uses a hook to turn it into an animatable, and just spits
out the view as if nothing happened. All of this in the same component, with
small, re-usable utility functions tapping into the host-components
lifecycles. Just by looking at it you know what it's doing.

 _This_ is how a component should behave from now on, an entity that hooks
into the host, instead of a class the host calls into. In my experience this
is the first time you can express logic linearly: x > y > z > view, without
wraps, inversion of control and DI.

Now imagine doing this with hocs ... even stacking two is already destroying
any hope of guessing what's going on.

------
Stoids
Perhaps I'm just a bit dumb here, but a few of these comments confused me.

> However, this is a common source of mistakes if you’re not very familiar
> with JavaScript closures.

> The problem is that useEffect captures the count from the first render. It
> is equal to 0. We never re-apply the effect so the closure in setInterval
> always references the count from the first render, and count + 1 is always
> 1. Oops!

Perhaps I'm not understanding the second comment fully... This behavior is
completely counter to how Javascript closures work in my mental model. I'm
trying to figure out how a state hook differs in execution from a normal
variable in module scope.

How does it differ from this [1] example.

[1] [https://jsfiddle.net/xuz09cow/1/](https://jsfiddle.net/xuz09cow/1/)

~~~
jakelazaroff
The difference is that closedVariable would be created _inside_ intervalFn
(the component). In addition, the intervalFn may run many times, but the
interval will only be set up once. This example illustrates the problem:
[https://jsfiddle.net/ov2msa4e/](https://jsfiddle.net/ov2msa4e/)

Your code avoids the closure problem, but closedVariable would be more like a
static variable — it'd be shared by every instance of the hook. Probably not
what you intended!

~~~
Stoids
Thank you for the example, Jake! It was very helpful. You and Jason's answer
cleared it up for me.

------
saagarjha
> Our “impedance mismatch” is not between Databases and Objects. It is between
> the React programming model and the imperative setInterval API.

I haven't used it much, but React gives me the feeling that it's trying to
come up with hacks to shoehorn imperative UI (which seemingly can't go away)
into the "the state is the UI" model. Anything with timers, or animations, or
other things that reach across multiple renders seems to be poke through the
nice React model because it by definition requires dropping the abstraction of
the UI being defined by the state because the change is more "incremental"
than transitioning to a new value.

(As an aside, I love the dark mode support on your website!)

~~~
sudhirj
> React gives me the feeling that it's trying to come up with hacks to
> shoehorn imperative UI

React IS a hack to shoehorn declarative UI into an imperative language and DOM
system. All further progress in the field is going to be a collection of
hacks.

ReasonML, Elm etc are clear ways forward that attempt to start from a clean
slate, but you're trying to do declarative UI on JS+DOM, everything is going
to be a hack. React is just the nicest currently available hack.

~~~
jakelazaroff
By this logic, though, _everything_ declarative is a hack. Computers are
imperative. At some point everything becomes an instruction; dig deep enough
and all declarative code calls imperative code.

------
elyobo
Pet peeve: using `let` when you could use `const`. I've been conditioned to
read `let` as a statement of intent to mutate that variable and it irks me
when they then... don't.

------
inglor
The funny thing is that with reactivity like Vue/MobX you don't actually
_need_ to make setInterval (or any other function) declarative - the change
detection system abstracts all this from you and you have one less concern to
care about everywhere rather than having to do this manually in terms of hooks
for every component.

Everything is just JavaScript (well... TypeScript) and the abstraction is
solid enough none of this is a concern you have to deal with.

(I enjoyed reading the article - the writing was solid :))

~~~
ryansolid
That's true. In one sense perhaps hooks are trying to hard to model a
different paradigm to what react runs on. But its interesting to see this
approach as it applies this pattern to other change detection mechanisms not
typically associated with fine grained change detection. Ive seen libraries
apply hooks to like LitHTML in webcomponents. Its the first time Ive seen the
potential for a universal API here. How sweet would it be if library writers
could target hooks instead of a specific framework?

------
diegof79
Perhaps someone that knows more about hooks can help me to understand the
trade-offs.

After looking at the intro: [https://reactjs.org/docs/hooks-
overview.html](https://reactjs.org/docs/hooks-overview.html)

My understanding is that each time that you call useState the renderer is
keeping track of a state associated with the currently rendered component.

It’s a clever solution, but that implicit context bothers me a little bit. For
comparison, Flutter makes the state relationship explicit:
[https://docs.flutter.io/flutter/widgets/StatefulWidget-
class...](https://docs.flutter.io/flutter/widgets/StatefulWidget-class.html)

The Flutter approach is more verbose, and I don’t know if I like it or not.
But the useState looks like a maintenance nightmare. There is no way to type
that function usage correctly, and the dependency is not explicit (making
refactoring and testing cumbersome).

Any experiences to share?

------
Jasper_
My eyes glazed over at this. I can follow the first code example with the
explicit mount and unmount, but really not any of the others. I will pray I
don't have to debug any website using these hooks features any time soon. This
"declarative hook" ivory tower is for people with far much more time on their
hands than I, I feel.

~~~
karolist
I've seen the sentiment expressed before and I think it will only get worse in
the future. React now is very popular with smaller/medium companies, large
enterprises like banks will surely jump to it after some delay. It's usually
when the main talent moves off to something more exciting (what happened with
rails -> node for example).

Even now it's hard to find someone good at idiomatic React, imagine the same
situation when it's perhaps not as popular as it once was and internal
projects are mature.

Codebases using declarative style are very hard for majority of programmers to
grok in my experience, mostly because it's such a big paradigm shift and
requires truly abstract thinking, also not generally something you learn at CS
programs. It will either all be a big mess or require some senior high
salaried people do the maintenance. That will be a good time to do frontend
contracting for the enterprises and it's coming soon.

~~~
erikpukinskis
I find declarative style hard because you can’t use a debugger, which means
you can’t “go in and look around”. You need to have a theory of what’s
happening a priori that you can test. That means you need to model the
internals of the tools in your mind to be able to make guesses. That means a
slow learning curve.

That’s fine if it’s one of one or two major libraries that you use all the
time every day. But in practice most of us are using dozens and dozens of
tools, tools which change all the time, and we have to learn as we go. So
these “big learning up front” APIs are just not very ergonomic for teams to
engage.

Unless you’re Rails or React or something where you can claim to be basically
a programming language or “standard library” in your own right, something
everyone on a team will have already dedicated themselves to studying in
depth.

------
indeyets
My issue with hooks is the learning curve for novices. Previously, it was
always easy to have answers to "how does this work?" questions.

Now, hooks give a lot of powerful ui l magic which lets programmers be super
productive from day one, but we have a need for articles like this to describe
things. It is not trivial anymore. A bit like rails-vs-sinatra story. React
arrived to the magical stage and we already had ember for that. React was nice
because there was no important magic.

Ah… who cares? I still gonna use it. It just means I will have to hire more
senior devs instead of juniors.

~~~
danabramov
Your experience might be different but I’ve seen many beginners who struggled
with classes pick up Hooks very quickly. A lot of the confusion has to do with
_unlearning_ a different mental model. Experienced programmers tend to get
more confused by Hooks than beginners.

Yes, there are pathological cases like this one. Somebody will put this
useInterval on npm and then it won’t be a problem. I’m not convinced it be a
big issue for beginners in practice based on my experience showing it to
people and seeing them play with it.

------
panic
Resetting the interval when the delay changes is probably not what you want
either -- quickly changing the delay (even a little bit) won't give the timer
a chance to fire. It would be better to compute a deadline internally, then
move the deadline forward or backward according to how much the delay changed
since the last call.

~~~
danabramov
Good thing something like useInterval() can abstract these implementation
details away so it can be made more precise with time. :-)

------
mrccc
I found this talk from React Day Berlin quite helpful for understanding React
hooks:
[https://www.youtube.com/watch?v=p8v9X2TNQKA](https://www.youtube.com/watch?v=p8v9X2TNQKA)
(An Exploration of Reacts Exciting New Features - Ken Wheeler)

------
crooked-v
I'll repeat what I've mentioned several times with things like this, which is
that to me hooks just feel like a more convoluted reinvention of mixins.

~~~
barryhoodlum
Hooks are composable, mixins are not.

------
dakom
Seems to me that deps are to (some) hooks as props are to components?

~~~
mst
That's ... really not a bad first order approximation at all, and I'm going to
steal it when trying to explain things to people. Thanks!

