Remember the good old days when you could just use a UseEffect() hook, define an async function with useCallback() and call it inside useEffect(), ...
For further actions, you may consider blocking this person and/or reporting abuse
Though I have used the server side data fetching myself and really like it, I still think that it would be nicer to have code examples for each way of data fetching, then give your in-depth knowledge of each pros and cons, rather than just write down your opinion like this.
I’m looking forward for some extend version of this post with code examples so everyone can discuss together.
This post is just an overview of different data fetching methods and their pros and cons. Including code would make it unnecessarily long and you can make it as complicated or as simple as you want depending on how in-depth you want to go. from implementing all the functionalities using useEffect() + useState() to implementation details of data fetching libraries that each need a whole new article (eg. for RTK Query: setup, using together with a Redux store, excluding from redux-persist, useQuery, useLazyQuery, useMutation, tags, etc.). you can even introduce a state machine like XState. As for Server Components and Server Actions, they are just async functions that run on the server and you can do whatever you want in them (read/write to a database, fetch data from API, transform data, etc.). again there are implementation details, like configuring caching and revalidation for fetch requests, streaming using Suspense, optimistic updates using useOptimistic(), or even using libraries like next-safe-action.
And I've not even mentioned Next.js API routes since they're just another layer in the middle and depending on whether you want to give access to other apps and the complexity of your application you can opt to use them.
For in-depth implementation details, I suggest referring to each section's links and reading more about them.
I agree. We're not looking for massive detail but even just a sketch of the new and old ways would have been useful (I came here looking for server action info). The title of the article suggests that there is a "how to" coming, that doesn't materialise, thus, the title could be updated to say it's an "overview" for clarity.
I have now added code examples and more explanations to each section. While it doesn't cover everything, hopefully, it'll provide some information about implementation details and common use cases.
You don't need to include 'setData' in the dependency array of 'useCallback' - this is peculiar. React guarantees that state setters never change. There's no need for 'useCallback' there at all. You don't need to include 'getData' in the dependency array of 'useEffect' either. This code has a noticeable odor.
You are correct that state setters don't change and are not needed in the dependency array. I have removed
setData
from it.About the
useCallback()
, other than the fact that not using it will result in the function unnecessarily being recreated on every render, generally, you don't fetch a static'url'
string and instead use a prop or state variable like userId, productId, etc. to fetch the data in which case you will need to use theuseCallback
hook. You can move the function definition inside theuseEffect()
and get rid of it but I personally prefer to have my functions separate.More info: React Docs: Is it safe to omit functions from the list of dependencies?
Also, this is a good example of why you wouldn't want to manually write the data-fetching logic yourself and instead use a library. and why server components are a great addition because they allow you to fetch the data without using any hooks.
Note: I rarely set the dependency array manually and use ESLint to fill it instead (react-hooks/exhaustive-deps). It used to complain about any function/variable used in the hook that was not in the dependency array (I think even the state setters) but now that I checked again it doesn't do it anymore. so it could be the source of some of the confusion.
Note: React Forget will fix some of these problems by remembering/caching all functions by default.
If you prefer to keep functions separate, you can simply declare them outside the scope of your component. Typically, request handling logic containing fetch or axios should reside in a separate module and be imported. This way, it won't be recreated on every render, and you won't need to use useCallback. However, there is no significant performance gain in avoiding small function recreation unless you are passing it as a prop to another component. Then, the fetching logic that contains state should reside in a separate hook that returns that state, which either success or error data. Including in the dependency array only parameters that clearly could change gives you a clearer mental model of how your code works. Adding instead the entire function to the deps and wrapping it in useCallback is awkward and mentioned as a 'last resort' even in those legacy React docs.
Anyway, the bottom line is - yes, there is little reason not to use a dedicated fetching library nowadays.
Since there's a need to manage the state in the function it can't be declared outside the component (you can move the fetch call to an outside function, but you'd still need to call that in your inner function and manage the state similar to what it is now)
I agree that if someone wants to manually code the fetch logic, it's better to make a custom
useFetch()
hook and use it instead for easier usage and cleaner code but custom hooks re-run at every re-render and the function will still be recreated on each render (unlessuseCallback()
is used). plus with a custom hook, you'd be passing the url as a prop now (useFetch('url')
) and the function has to be added to the dependency ofuseEffect()
and as a result has to be wrapped inuseCallback()
.example hook:
usage:
While as you said, the overhead for recreating a small function is not much, I prefer not to let that happen. my approach is that if the function is not using the state I define it outside the component (or move it to a
util.ts
file) and if I define a function inside the component, I always useuseCallback()
to memorize it."Since there's a need to manage the state in the function it can't be declared outside the component" - It (and everything in the functional world) totally can, you just need to pass your own resolve / reject callbacks. And nothing will be recreated in "useFetch" hook if you declare it inside useEffect. Example of the proper useFetch hook - usehooks-ts.com/react-hook/use-fetch. Notice another important part there - only one state is possible, like "loading", "fetch", or "error" instead of isLoading/isError booleans gymnastics (see more kentcdodds.com/blog/stop-using-isl...)
It's technically possible (by passing all state setters (or
dispatch
if usinguseReducer()
) to another function and controlling the component's state externally) but it will make reading the code harder than using auseCallback()
hook and would only make sense if you'll be using that function in multiple different hooks/components.StackOverflow: Pass a state setter as an argument to an async function
If you were to do that in the current hook (don't do it :)):
I think I have mentioned multiple times by now that declaring the function inside the
useEffect()
will allow you to drop theuseCallback()
and you would only need it if you define your functions outside theuseEffect()
.I've checked that
useFetch()
hook before, and it is indeed better. but I'm not trying to write a "proper"useFetch()
hook and there are most likely more problems with things like error handling in my code. I'm just trying to demonstrate what fetching in a React component would look like without using a library like RTK Query, Server Components, or Server Actions. (I'm using the same state variable names as the ones returned by RTK Query'suseQuery()
hook)As the
useFetch()
hook you linked shows, when managing multiple state variables, using theuseReducer()
hook is a better solution, and then as the article you linked mentions (and as I mentioned in one of my comments around a month ago) you will most likely want to use a state machine like XState instead."If you were to do that in the current hook (don't do it :))" - sure, don't do it this way and this is not at all what I meant, it could be abstracted away with only 2 callbacks.
It may very well be my skill issue but I don't see how that would work. As callbacks, promises, and async/await work the same (throwing in an async function is
reject
and returning a value isresolve
), and you still need to manage the state somewhere in your code. (unless you mean using thethen().catch()
syntax instead of async/await or something)good article man, so, if i have a page with few components and inside each component, i do 3 fetchs (yeah, its real), which approach is more performant ?