I would expect a bit more in-depth article from Martin Fowler website.
The shown implementation is naive and prone to bugs, for example a race condition. If this id is rapidly changed (say from 1->2), and the first request arrives last, you think you are looking at user 2 but you've loaded data for user 1.
you mean user1 button? It would throw away the previous http request and users 1 data wouldn't even show up
Hiding the elements/div would prevent the user from clicking that button.
I don't see how it is 20/80.
Switchmap is just a different way of doing things, and hiding the deiv instead of blocking doesn't add really add more effort as well.
So I don't see how it's a lot of effort while also missing a lot of the desired features?
If you are going to block further activity, to be safe block future requests in code too (a check on the event handler that might make the request most likely) in case someone tries to automate the app via browser extension or bookmarklet. Just blocking clicks with a UI overlay doesn't always cut it.
Or in case some fool on your dev team calls the events to automate (or for other reasons trigger) something that wasn't intended to be called that way, of course, though once you start defending against iffy practise from within you are fighting a battle you are going to lose!
UseMemo should not be used for fetching/kicking off a fetch either. UseMemo fans should be pure. Using logic that belongs into useEffect (logic that happens _outside_ the reactive flow) could potentially lead to other side effects which are very hard to debug. Just a example: a lot of fetch implementations are using fetch with a cache triggered in useMemo returning immediately. You will probably have a setState somewhere in the flow which will terribly interrupt react and break your page.
In case you trigger a native fetch, you've got no way to cancel the call due to the missing cleanup fn.
> UseMemo should not be used for fetching/kicking off a fetch either
Wrong. That's just like, your opinion.
The best time to kick off external calls is during first render, not after the component is mounted and React has gotten around to calling your useEffect callback.
useMemo can be a part of the solution along with useRef and useEffect cleanup(remember it's essentially to the unmount lifecycle hook as well).
As in useMemo to make API calls? That is definitely not correct. React explicitly makes no guarantees about how infrequently the fn is called or how long the value is cached for.
These days unless a react app needs to do something really special, I always reach for and recommend others reach for Tanstack. Currently have a react frontend that's like 50k LOC and it's been chugging with Tanstack Query since day 1 and we have had zero regrets. It's truly one of the best React libraries out there right now.
I know that Martin Fowler is your God, and that my denigrations upon this divinity will not go unpunished, or that this guy is not Martin Fowler, but what has this guy ever build to convince me that he actually knows what he is talking about? Maybe I am missing that or something else, but here I read that "Today most applications can send hundreds of requests for a single page", like one would hope there to be a sort of role within the software enterprise that would actively try to discourage people from being stupid like that.
Hey, at least when DHH jumps on the third rail of the hypermedia bandwagon I can laugh about the 500ms delay of a dropdown to show a calendar in his mail app, and I respect him for that.
I agree that there are some ill conceived resources out there but there are some who walk the walk and talk the talk:
- Erich Gamma contributor to eclipse as well as JUnit (Design Patterns: Elements of Reusable Object-Oriented Software)
- Kent Beck key contributor to JUnit
- Joshua Bloch key author of the collections API in java (Effective Java)
- Donald Knuth
- James Gosling creator of Java and the Java programing language book
> Maybe I am missing that or something else, but here I read that "Today most applications can send hundreds of requests for a single page"
I liked the following line better: "The main reason a page may contain so many requests is to improve performance and user experience, specifically to make the application feel faster to the end users."
The author is a dev at Atlassian. If he works on Jira or Confluence, that would explain so much. The two apps are unusable at enterprise level of data. Insane page load and render times up in the 25+ seconds range for complex tickets and pages.
I am horrified. I didn't see mentioned, proper data fetching _must_ happen before UI rendering if you want to have consistent behavior with classic HTML/HTTP apps, e.g. the current page content stays displayed until the received html starts to be rendered (with the actual data for pure HTML). You loading feed back _has_ to happen from the interacted page in first intent.
If you want to have an after routing/rendering loading feedback, such as skeleton or so, you still can do it, but it will be opt-in, not an after thought of savage data fetching and rendering that really might happens as the application gains features and diverts from the simple POC patterns.
Proper data fetching and rendering cannot happen without a router. Remix solved this with their updated react-router, I know that this router has a bad rep with breaking changes, but they finally landed the implementation that neatly cover most if not all the use cases for routing with dynamic code imports and data fetching.
> if you want to have consistent behavior with classic HTML/HTTP apps
Why is that desirable? I see it more like a limitation of server-rendered web apps. Many times you can show useful UI before actual data is fully loaded (i.e. search / filter controls or basic data you already have).
Certainly, cascading fetching is undesirable and you should know what data you need to fetch and start the requests as soon as possible, but not necessarily before the route transition occurs.
>Many times you can show useful UI before actual data is fully loaded (i.e. search / filter controls or basic data you already have).
It can be done, yeah, but it has to be handled very carefully because it runs the risk of causing more issues than it solves. I can't count the number of times I've used the AWS Console, loaded a page, clicked on a control, only to find that the data that was loaded a few milliseconds before caused the elements on the page to shift so now I clicked on a completely different button, started the process to load a new page and now have to reload the old one and wait for the entire page to finish loading or risk a misclick again.
We mostly agree, my point is more about risk management of uncontrolled loadings. I think it's better to naturally land on consistent, known mechanisms, in the idea that it simplifies code as first intent, and then adjust your preferred/desired implementation case by case.
For example I interact with a medical app. When you open a patient card, there's lots of data getting requested. It halts the new context opening for a long time, even though you need only a fragment of this data. I'd love it if it didn't wait and instead opened the basic information with placeholder blocks that get loaded as the data comes in. You really don't need all the old visits loaded in a collapsed tree that you're not going to use. This would save significant amount of time ever day.
The problem you describe is more about alignment between implementation and use cases. e.g. if your main use case is to have the data displayed on screen to be loaded, then that should be loaded. If it's desirable to preload other data in the background for later screen updates then just do that. The idea is more about first intent, load what is displayed on screen, if it's better to have parts loaded incrementally, then just do that.
That's why I was asking about you stressing "must". In that case it's a mix of fetch before the screen, fetch to replace placeholders, fetch details on demand. That sounds like it breaks your "most fetch before rendering" rule.
How much is lots? I would think for a single patient, you'd generally be requesting maybe a couple dozen kB unless there are images, but those should be loaded separately. Looking at the Crowdstrike thread from the other day on this site for example, it looks like 500 comments amounts to ~120 kB and loads in about as many ms (and that's with gzip. br or zstd should do better).
It's not the size. The main view contains bits from so many tables and in situations where the context decides how to calculate other results. Compression is cool, but it can't solve running a chain of queries.
If you can’t quickly collect data for a principal view, the data structure is incorrect. This doesn’t solve your problem cause it’s usually unlikely that one can refactor a data problem on sight in a shipped app, but doesn’t make this case normal too. Most apps don’t need such partial loading optimization and shouldn’t be designed as such. Normalizing it creates a web full of never-ending spinners, which already is a plague.
It's medical software which existed for over a decade. The main view for the patient includes billing details, family connections, reminders, previous booking, documents, outstanding tests, pregnancy status, preventive care preferences, etc. There's lots to optimise in this app/db for sure, but you're underestimating the real complexity. Whatever you do, you're still querying 20+ tables (in non trivial ways) and calculations.
Of course one does not simply rewrite an existing system, but for what it's worth, you could still make that fast to query by doing xml or json queries with subselects. Some testing I've done with that technique with a mock social-media use-case hit ~15k requests/second end-to-end on pretty weak hardware (4 cores) needing to query 8 tables to construct a profile with 1M profiles in the database. I don't have the exact results saved, but IIRC it was a ~10 ms response time under load.
I've managed a database of local rescuers veterans, including their full PII, illnesses, statuses, family, etc. Even the floor they lived on and how much they drink was in there. It was one form per person with all the data, opened in an instant in a DBF-based system. The "trick" was to store all data in a single table with a few trivially indexed subtables and packed-table-blobs for tabular data. Shocking, I know. It's not "whatever you do", it's a matter of being RDBMS practicioner vs SQL pedant, which is where all the real complexity comes from. If data cannot be collected in a reasonable time for a primary form, it's bad design by definition. It's more reasonable to spend a few minutes to collect a report required once a year than spend a minute on a form required often.
Again, not criticizing you for this mess, but it's not something a generic ui framework should solve for.
It's perfectly possible and common to generate a server-side render with the data fetched, so it doesn't break any consistency with the client-server model.
Yes, that's also why it's great to use a router compatible with react SSR render capabilities, since you'll have a consistent and predictable behavior either from first rendering on the web client, or page change and loading on client js code.
The how is of course extremely simple. But when is the real question. You can do a full send on every route load, but at that point you might as well just do SSR. Dealing with stale data is a nightmare, but you want to have at least some client side inter-page caching to avoid redundant API calls. I've never found a perfect solution here.
The most common mistake I see is putting data fetching in the view layer.
React hooks caused this because by default there’s no sensible place to fetch data.
So unless you know ahead of time, any complex single page ends up with complex and hard to track network requests.
The solution is really simple: pull data out of the view layer and make it a first class citizen.
—
An exercise: imagine you’re building a tui instead of a web app, but you’re forced to use the exact same code for app state and data fetching… what would that code look like?
React has always just been the view layer. I'm not sure how hooks made the situation any worse, you're just using a hook (usually useEffect) instead of a class component method or componentDidMount.
I don't understand what it means to "pull data out of the view layer and make it a first class citizen". If you're writing a React app, you are building a UI and will need to handle data in your view layer somewhere. You either hand-off that works to a library like Tanstack Query or you manage it yourself.
The problems come when you modify state in response to rendering the UI. This seems deceptively natural (and in fact seems encouraged by on-line tutorials), but leads to "spaghetti fetching" when composing components together - it creates a "feedback loop" that modifies state just because some component happens to mount, which can then modify state further, then cause further mounting/fetching etc...
A better approach is to treat fetching as an explicit state change. If user clicks on a button, you do the fetch and modify the state. If that causes some components to be mounted - so be it, but does not cascade any further.
Let's say you want to show a modal, which fetches some data and modifies the state. Based on this, new children are rendered which again fetch state. The problem of "spaghetti fetching" becomes worse the more levels of recursive fetching there are. If I understand you correctly, you argue for fetching all data upfront, and then rendering the modal and all its children all at once. This way you ensure "UI = f(state)" by removing side effects from "f".
On the other hand, I can also see some drawbacks:
1. This goes against the idea of fetching data close to where it's used, basically promoting modularization.
2. From the POV of the children, you have to backtrack where their data are coming from.
3. If components always use the same data, you have to duplicate fetching their data everywhere you want to use them.
4. You can't partially show children, but have to wait for everyone to have their data before rendering them.
The article sort of touches on this, but in my experience working with React the most important thing is to keep as much as you can declarative. When you start doing things in an imperative way, particularly when they are async, that's when you run into problems. It's one of the reasons I find GraphQL such a good fit.
Bleugh. Who is this article for? (good bait from the poster) Anyone who's done any kind of meaningful SPA dev knows this pattern does not scale & for newbies, this is poison.
Instead of talking high-level framework agnostic code design and the multitudes of ways to fetch data (route based, state stores, authentication hooks, subscriptions, model composition , to name a few ); author goes deep into outdated React anti-patterns.
64 comments
[ 4.2 ms ] story [ 150 ms ] threadThe shown implementation is naive and prone to bugs, for example a race condition. If this id is rapidly changed (say from 1->2), and the first request arrives last, you think you are looking at user 2 but you've loaded data for user 1.
const [user, setUser] = useState<User | undefined>();
If you don't know what you're doing, use Tanstack Query libs or read a better article.- https://tanstack.com/query/latest
- https://maxrozen.com/race-conditions-fetching-data-react-wit...
better immediately hide user1 and show a loading button and use rxjs's switchmap
Also your proposed solution involves a lot more moving pieces.
Sounds like a 20/80 solution.
Hiding the elements/div would prevent the user from clicking that button.
I don't see how it is 20/80.
Switchmap is just a different way of doing things, and hiding the deiv instead of blocking doesn't add really add more effort as well. So I don't see how it's a lot of effort while also missing a lot of the desired features?
If you're going to start adding logic to hide other elements conditionaly it's prone to become spaguetti. And how would that scale code-wise?
Might as well dim the screen and show a loading spinner to convey to the user that they should wait, a fraction of a second on average.
Remember this is in the context of a LOB app. Not facbook.
Just show a loading spinner from an axios callback and be done with it. It's so fast anyway.
Other libs listen to these events to, for example, show loading spinner on request.
this is a trivial task, it's only going to spaghetti if you go out of your way to make that happen
Or in case some fool on your dev team calls the events to automate (or for other reasons trigger) something that wasn't intended to be called that way, of course, though once you start defending against iffy practise from within you are fighting a battle you are going to lose!
In case you trigger a native fetch, you've got no way to cancel the call due to the missing cleanup fn.
Wrong. That's just like, your opinion.
The best time to kick off external calls is during first render, not after the component is mounted and React has gotten around to calling your useEffect callback.
useMemo can be a part of the solution along with useRef and useEffect cleanup(remember it's essentially to the unmount lifecycle hook as well).
But triggering an api request having side effects is 100% incorrect.
awful but works.
Plus the article only refers to React, but it could have stayed a bit more abstract about the techniques.
Hey, at least when DHH jumps on the third rail of the hypermedia bandwagon I can laugh about the 500ms delay of a dropdown to show a calendar in his mail app, and I respect him for that.
I liked the following line better: "The main reason a page may contain so many requests is to improve performance and user experience, specifically to make the application feel faster to the end users."
If you want to have an after routing/rendering loading feedback, such as skeleton or so, you still can do it, but it will be opt-in, not an after thought of savage data fetching and rendering that really might happens as the application gains features and diverts from the simple POC patterns.
Proper data fetching and rendering cannot happen without a router. Remix solved this with their updated react-router, I know that this router has a bad rep with breaking changes, but they finally landed the implementation that neatly cover most if not all the use cases for routing with dynamic code imports and data fetching.
Why is that desirable? I see it more like a limitation of server-rendered web apps. Many times you can show useful UI before actual data is fully loaded (i.e. search / filter controls or basic data you already have).
Certainly, cascading fetching is undesirable and you should know what data you need to fetch and start the requests as soon as possible, but not necessarily before the route transition occurs.
It can be done, yeah, but it has to be handled very carefully because it runs the risk of causing more issues than it solves. I can't count the number of times I've used the AWS Console, loaded a page, clicked on a control, only to find that the data that was loaded a few milliseconds before caused the elements on the page to shift so now I clicked on a completely different button, started the process to load a new page and now have to reload the old one and wait for the entire page to finish loading or risk a misclick again.
It's not an intrinsic limitation of server-rendered apps. Apollo solves this https://www.apollographql.com/docs/react/performance/server-... as do many other data-fetching libraries.
Got any suggestions for where to look if I'd like to learn more about what that means exactly?
They combine react router pre-fetching with tanstack query.
For example I interact with a medical app. When you open a patient card, there's lots of data getting requested. It halts the new context opening for a long time, even though you need only a fragment of this data. I'd love it if it didn't wait and instead opened the basic information with placeholder blocks that get loaded as the data comes in. You really don't need all the old visits loaded in a collapsed tree that you're not going to use. This would save significant amount of time ever day.
I've managed a database of local rescuers veterans, including their full PII, illnesses, statuses, family, etc. Even the floor they lived on and how much they drink was in there. It was one form per person with all the data, opened in an instant in a DBF-based system. The "trick" was to store all data in a single table with a few trivially indexed subtables and packed-table-blobs for tabular data. Shocking, I know. It's not "whatever you do", it's a matter of being RDBMS practicioner vs SQL pedant, which is where all the real complexity comes from. If data cannot be collected in a reasonable time for a primary form, it's bad design by definition. It's more reasonable to spend a few minutes to collect a report required once a year than spend a minute on a form required often.
Again, not criticizing you for this mess, but it's not something a generic ui framework should solve for.
React hooks caused this because by default there’s no sensible place to fetch data.
So unless you know ahead of time, any complex single page ends up with complex and hard to track network requests.
The solution is really simple: pull data out of the view layer and make it a first class citizen.
—
An exercise: imagine you’re building a tui instead of a web app, but you’re forced to use the exact same code for app state and data fetching… what would that code look like?
I don't understand what it means to "pull data out of the view layer and make it a first class citizen". If you're writing a React app, you are building a UI and will need to handle data in your view layer somewhere. You either hand-off that works to a library like Tanstack Query or you manage it yourself.
UI = f(state)
The problems come when you modify state in response to rendering the UI. This seems deceptively natural (and in fact seems encouraged by on-line tutorials), but leads to "spaghetti fetching" when composing components together - it creates a "feedback loop" that modifies state just because some component happens to mount, which can then modify state further, then cause further mounting/fetching etc...
A better approach is to treat fetching as an explicit state change. If user clicks on a button, you do the fetch and modify the state. If that causes some components to be mounted - so be it, but does not cascade any further.
Yeah don't do that. Rendering shouldn't have such side-effects. Navigation might, but that's not rendering. Is it a common mistake with React?
On the other hand, I can also see some drawbacks:
I feel like there are trade-offs to be made here.It prevents the umpteenth reinvention of an incomplete fetch state machine, which is probably the number one most consistent frontend bug I encounter.
Instead of talking high-level framework agnostic code design and the multitudes of ways to fetch data (route based, state stores, authentication hooks, subscriptions, model composition , to name a few ); author goes deep into outdated React anti-patterns.
Everyone who has commented on this is spot on.
I understand why you'd use Tanstack Query or similar solutions most of the time. I wish the mental overhead of using them was smaller.