React.js is currently the most popular JavaScript library for front end developers. Invented by Facebook, but available as an open-source project, ...
For further actions, you may consider blocking this person and/or reporting abuse
Nice examples! I'm sure that you are aware of it, and know how to fix it. So for anyone else reading the article and my comment here: there's a small problem with the
useFetch
hook implementation.It currently would not react to URL or options argument changes, since the internally used
useEffect
doesn't specify them as dependencies, so the response result wouldn't change should the request change (e.g. different URL or different query parameters). In fact fetch only runs once on component mount, because the dependencies array is empty.In order to fix that the url and the options need to be added to the dependencies array as follows:
Referential Equality Problem
The problem with React dependencies is that changes are detected through referential equality tests. So a hook with dependencies only gets re-run when the following is true for one or more of the dependency values:
That works fine for primitive values such as numbers, strings and booleans.
In case of functions or objects such as the options parameter this is a bit of a problem. Even when the data in an object literal does not change, two instances of
{}
are not referentially equal. JavaScript would create new instances for those object literals on each render run.Three workarounds for that:
Let the user of
useFetch
take care of memoizing the options viauseMemo
, so it's "referentially stable" and does not change from render run to render run. But that would be sorting out the problem from the wrong end in my opinionFrom within
useFetch
, serialize the options argument as string, and add that string to the dependencies array. The serialization must be stable though, so we don't get different results between render runs (i.e. different order of properties)Use an use effect hook that is able to do deep dependency comparisons. Let's call it
useEffectDeep
. That could be either an exercise for writing another custom hook, or can be downloaded from here (or other places): github.com/kentcdodds/use-deep-com...Awesome, thank you very much for the detailed analysis!
🔥🔥
Great to see you are embracing the hook feature! And awesome you share your findings.
There are however a few small problems with your hooks.
useTimeout
anduseInterval
You are storing the callback into a ref which is great because you make sure you will allways call the latest version. You however set it using
useEffect
. This means if the callback changes, the change will be picked up in the next frame. Theoretically there is a chance that the callback has changed when the timeout (or interval) fires. You can fix it like this:usePrevious
This might actually be expected behavior, but this does not actually show the previous 'different' value. If you component rendered again with no value change
usePrevious
would return the same value. So the actual name should beuseValueFromPreviousRender
.You could see this behavior when you add another state to the
MoneyCount
component that toggles a boolean when you click another button. The hook would be a bit more involved if you wanted to show the previous value after a change.useClickInside
anduseClickOutside
This hook adds and removes the event listener on each render. It would be smart to use a similar strategy to
useTimeout
with a ref for the callback. On top of that, its a good idea to set the dependency array.As a side note: in theory the ref you are given could be a function ref, if that is the case your code would fail. The easiest way to guard for this is by creating and returning the
ref
from the hook itself.useFetch
I think this is already mentioned but it only executes once during the lifetime of the component.
In real world applications you often want to know the response status code. I think it would be a great addition to add that.
useComponentDidMount
anduseComponentDidUnmount
These hooks capture the callback that is passed in. This means that if the callback changes, those changes will not be picked up. For
useComponentDidMount
this will most likely never cause a problem, foruseComponentDidUnmount
I can see this causing problems more often.If someone in my company would create these hooks I would say that these hooks do not have the correct name. Their names refer to the component lifecycle and that is something hooks helps us to move away from. We want to focus on the actual effect and its lifecycle. On top of that, for new developers (that have only been exposed to React with hooks) the concepts of mount and unmount are unknown.
All in all great examples. I hope my comments are of value to you. I strongly suggest you install the eslint plugin as this would warn you for most problems.
Nice comment! But I have a puzzle: why borther to use
I think we can eliminate the use of
useRef
, just use thecallback
argument in the placecallbackRef.current
.@wangyouhua Great question: What is the difference between these versions?
You can see the difference in the usage, in this example I want to log the value of
someState
after 5 seconds:As you can see version 2 and 3 have a bit different behavior. Version 2 is broken (and caught by the linting rules). Version 3 has unexpected behavior, it resets the timeout when
someState
is changed.This has to with the 'capturing' of the callback. Once you pass a function to
setTimeout
that function can not be changed. Primitive values it references are captured. In order to get the expected result and don't put the burden of that correct handling on the user of the hook, we keep a reference to the 'current' callback.So if you are using callbacks in hooks this is a great pattern:
Thank you very much for this detailed comment, this helps a lot!
blog.post.replace(/pictures/gi, 'code'));
I think I would break the
loading
out ofres.response
inuseFetch()
.Why? Because if you load something else (a different URL), then
res.response
already exists and won't cause the "loading..." to be shown... so the loading flag really needs to be before the start offetch()
and aftersetData()
useComponentWillUnmount
this way (in the image), then won't it be true if the component is rendered 20 times, then useEffect() sets it up 20 times to run it 20 times? That's because you are callinguseEffect()
20 times in totalHi, I enjoyed reading this. I didn't find use cases for all of them in a recent project. I'm using a Mediator pattern to handle fetch requests. I did repeat my self !DRY. But the abstraction is a refactor for another iteration.
I do agree with the comments which point out the errors. I would suggest making the fixes and updating this article. There's an option which allows you to do that.
And if you haven't already done so. Check out React Server Components. I'm sure you love them.
Hi Simon,
Thanks a lot for the examples ill definitely use them!
Do you have a repo where you store the code? If you only share images I cannot use them unless I type them again.
Hey, glad you like them. I wanted to try something different this time and animate others to actually do type it on their own. But I will prepare a short repo with the code!
Here's the one for useTimeout: codesandbox.io/s/youthful-spence-n...
You threw me off with the variable named seconds since it only increments every 5 seconds and then executes only once so I wanted to check it out.
Working code examples with posts like this would be helpful
Will make it that way next time 😊
Hi. Nice article, thx a lot! Very useful for newer like me😊
ComponentDidMount and ComponentWillMount are now marked as unsafe to use on the latest version of React.JS
The useTranslation hooks is awesome too 😉 dev.to/adrai/how-to-properly-inter...
Great article! These examples are really useful. For the useTimeout and useInterval hooks though, I think they can be simplified with the useCallback hook. Or was there a reason for not adding that?