[HN Gopher] Data Fetching for Single-Page Apps
___________________________________________________________________
Data Fetching for Single-Page Apps
Author : fagnerbrack
Score : 90 points
Date : 2024-07-22 01:31 UTC (21 hours ago)
(HTM) web link (martinfowler.com)
(TXT) w3m dump (martinfowler.com)
| morbicer wrote:
| 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.
|
| const [user, setUser] = useState<User | undefined>();
| useEffect(() => { const fetchUser = async () => {
| const response = await fetch(`/api/users/${id}`); const
| jsonData = await response.json(); setUser(jsonData);
| }; fetchUser(); }, [id]);
|
| 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...
| hu3 wrote:
| For line of business applications, a quick 80/20 solution is
| putting a spinner blocking the screen when I click the [edit
| user 1 button].
| kitkat_new wrote:
| sounds more like 20/20 solution ux-wise
|
| better immediately hide user1 and show a loading button and
| use rxjs's switchmap
| hu3 wrote:
| How would rxjs's switchmap prevent the user from clicking
| user2 button?
|
| Also your proposed solution involves a lot more moving
| pieces.
|
| Sounds like a 20/80 solution.
| kitkat_new wrote:
| 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?
| hu3 wrote:
| > Hiding the elements/div would prevent the user from
| clicking that button.
|
| 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.
| sabbaticaldev wrote:
| why axios? JS has native fetch everywhere for ages. let's
| remove the moving pieces.
| hu3 wrote:
| Agreed. I'm starting to replace axios with a simple fetch
| block that emits start/stop/error events.
|
| Other libs listen to these events to, for example, show
| loading spinner on request.
| Rapzid wrote:
| Also useEffect callback is run _after_ the component mounts. I
| often use memo when kicking off async processes on component
| mount.
| donjoe wrote:
| 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.
| madeofpalk wrote:
| 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.
| knallfrosch wrote:
| The missing syntax highlighting (or the absence of _any_
| highlighting) makes it really hard to read.
|
| Plus the article only refers to React, but it could have stayed a
| bit more abstract about the techniques.
| FjordWarden wrote:
| 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.
| vrnvu wrote:
| Those who can't do, teach.
| keb_ wrote:
| Yep this is why I refuse to read any text books or
| documentation. If they were any good, the author wouldn't
| have written it!
| nin247 wrote:
| 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
| 91bananas wrote:
| Also written as, "have done it, it's really boring, maybe
| teaching other people how to do it will be less boring"
| chimpanzee wrote:
| This saying is a virus.
| azangru wrote:
| > 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."
| sigseg1v wrote:
| 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.
| janci wrote:
| JIRA is the worst combination of slow, blocking load before
| the view switches and sequential n+1 loads of more data that
| goes on and on
| tuyiown wrote:
| 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.
| janci wrote:
| > 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.
| leovingi wrote:
| >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.
| beejiu wrote:
| > I see it more like a limitation of server-rendered web
| apps.
|
| It's not an intrinsic limitation of server-rendered apps.
| Apollo solves this https://www.apollographql.com/docs/react/p
| erformance/server-... as do many other data-fetching
| libraries.
| tuyiown wrote:
| 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.
| Bjartr wrote:
| > Proper data fetching and rendering cannot happen without a
| router.
|
| Got any suggestions for where to look if I'd like to learn more
| about what that means exactly?
| tuyiown wrote:
| It's a bit dense, but you'll have a broad overview in react
| routers docs https://reactrouter.com/en/main/start/overview
| viraptor wrote:
| Why do you go with "must"?
|
| 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.
| tuyiown wrote:
| 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.
| ndriscoll wrote:
| 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).
| beejiu wrote:
| 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.
| tuyiown wrote:
| 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.
| ramesh31 wrote:
| 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.
| ibash wrote:
| 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?
| boredtofears wrote:
| 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.
| branko_d wrote:
| The original promise of React was:
|
| 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.
| aatd86 wrote:
| >The problems come when you modify state in response to
| rendering the UI
|
| 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?
| boredtofears wrote:
| Yes, that's right. The official React docs explain this
| pretty clearly.
| kherud wrote:
| 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.
|
| I feel like there are trade-offs to be made here.
| postalrat wrote:
| Did the original react only support stateless components?
| wooly_bully wrote:
| The sensible route to this today is to use a query client, IMO:
| tanstack, rtk query, apollo, etc.
|
| It prevents the umpteenth reinvention of an incomplete fetch
| state machine, which is probably the number one most consistent
| frontend bug I encounter.
| beejiu wrote:
| 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.
| samradelie wrote:
| 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.
|
| Everyone who has commented on this is spot on.
___________________________________________________________________
(page generated 2024-07-22 23:10 UTC)