[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)