Warning: React 19's use Hook Can Impact App Performance

Поделиться
HTML-код
  • Опубликовано: 3 авг 2024
  • React 19 and the NextJS App Router have the use hook, and it's great. We can now await promises in client components. But there are some gotchas with it that we need to explore.
    Code: arc.net/l/quote/eokiyxsr
    👉 Upcoming NextJS course: pronextjs.dev
    👉 Don't forget to subscribe to this channel for more updates: bit.ly/2E7drfJ
    👉 Discord server signup: / discord
    👉 VS Code theme and font? Night Wolf [black] and Operator Mono
    👉 Terminal Theme and font? oh-my-posh with powerlevel10k_rainbow and SpaceMono NF
    00:00 Introduction
    01:20 Tacking Tricky State Resets
    02:59 How React Flips Component “Modes”
    04:26 Don’t Worry About This Too Much
    04:53 The Behavior Changes After Render
    06:40 How use Impacts Performance
    10:04 Hoisting Fixes Everything
    12:15 Outroduction
  • НаукаНаука

Комментарии • 87

  • @jazzmaster89
    @jazzmaster89 26 дней назад +23

    Great video. This is completely asinine though by the react team. Imagine how many devs aren't going to be aware of these nuances. What are they thinking?

    • @PhilipAlexanderHassialis
      @PhilipAlexanderHassialis 26 дней назад +4

      I am suspecting that this is a "compromise" solution, trying to brute force promises handling inside components that are *theoretically* supposed to be entirely synchronous. Consider that the "use" function doesn't abide by the "rules of hooks". Hence I postulate that they named it that way to make it "easy" for the rest of the community who has been spoonfed to know that if something starts with the word "use" it's a hook.

    • @jherr
      @jherr  25 дней назад +3

      They are probably thinking that they want to be cautious to maintain 100% compatibility with previous behavior while still offering new functionality. The key takeway in this video is that you should hoist your promises when using Suspense/use. In that case, as demonstrated, this isn't an issue.

    • @SeanCTTR
      @SeanCTTR 22 дня назад

      I mean, that could be kind of a problem with anything in any language. Including so many things that I’ve already been in react for years. It’s why so many people complain about hooks and useEffect. Even though you can just use them properly and they work fine.

    • @jherr
      @jherr  22 дня назад

      @@SeanCTTR Yeah, if you don't fight the framework, stuff usually goes pretty smoothly. In this case, hoist your promises outside of the suspense and you'll do fine.

  • @0xAndy
    @0xAndy 26 дней назад +9

    This is the first time I've felt like leaving the React ecosystem in almost a decade.

  • @rodrigorb2630
    @rodrigorb2630 14 дней назад +2

    Man, I feel for folks trying to launch and maintain courses on React in 2024

  • @blizzy78
    @blizzy78 26 дней назад +2

    I love these in the weeds videos, keep em coming

  • @odra873
    @odra873 26 дней назад +11

    I just use any of the api libraries and don’t worry about any fetch problems 😅

  • @o_glethorpe
    @o_glethorpe 27 дней назад +9

    0:30 I thought React 19 was created to solve exactly this kind of problem. They just can't make React not suck. Will try again when 20 comes out.

    • @jherr
      @jherr  26 дней назад +5

      You might be thinking about server components. And server components do this just fine. But server components can't have state, or be run directly on the client.

    • @o_glethorpe
      @o_glethorpe 26 дней назад +1

      @@jherr Hi, thank yout for the video, I was talking about client side, in the first 40 seconds of the video, you describe what is to me the logical way of doing this, and react being react, you need to "work around" to prevent that, the logical way, to create issues. I mean, came on, they say the problem is me, I dont have the right mind-set to work with react... maybe they are right, as a software developer, if the logical way of doing something is not the right way to do it, then this is not for me. I think I will keep trying tho.

    • @tnsaturday
      @tnsaturday 20 дней назад

      React 19 is to solve rerenders problem by automatically using useMemo, useCallback and memo. It's not about async components. That's what Next.js is all about.

    • @jherr
      @jherr  20 дней назад +3

      ​@@tnsaturday Just to clarify. The React Compiler isn't part of React 19. It was launched around the same time, which has led to some confusion. But React 19 and the compiler are two separate things. You can actually use the compiler with 18 if you shim one simple hook.
      Also, the React compiler doesn't "solve rerenders" since rerenders are a critical part of the React component lifecyle. It does create code that has more optimized re-rendering and which will avoid unecessary re-renders of portions of the component.
      The point about sync/async component was to distinguish that I was talking about using `use` in the client context. Either as as a SPA, or as a client component in a NextJS app.
      These issues don't apply to NextJS in an RSC -> client component context because async requests are inherently hoisted into the RSCs in that architecture.

    • @tnsaturday
      @tnsaturday 20 дней назад

      @@jherr Thank's a lot for clarification!

  • @versaleyoutubevanced8647
    @versaleyoutubevanced8647 27 дней назад +6

    I'm some of the folks that like these type of content ❤

  • @mertdr
    @mertdr 26 дней назад +3

    Great content Jack! I think passing fetch results to another component in order to fix “use”hook’s behavior ends up with unpractical decoupling. And these examples don’t even cover refetching and loading state (currently loading through suspense), which probably would require passing other props as well. I guess React Query will continue to shine for fetching data in React 19 as well.

    • @jherr
      @jherr  26 дней назад +3

      FWIW RQ has an external cache that’s doing a bunch of heavy lifting to give you the impression that your data access is local to the component.

    • @mertdr
      @mertdr 26 дней назад +1

      IMO it’s another win for RQ since it stores cached data in context and allows to pull cached or fresh data by query key on further requests. I suppose “use” wouldn’t have sort of opportunity because data will be refetched on component recreation (route change or mount/unmount). Maybe there will be a helper function for caching like nextjs’ next/cache (currently experimental)
      Honestly it’s been almost a year or so since “use” introduced as an experimental hook and this video is probably the first one deep dives its use cases. Thanks a lot for your effort.

    • @jherr
      @jherr  26 дней назад +2

      @@mertdr To really use `use` you need to have a cached fetched. And you could whatever you want for that cache. Yeah, RQ is good stuff. But I think having a general way to handle promises in the framework seems like a good idea overall.

  • @fexxix
    @fexxix 24 дня назад

    From this video I have learned that react-query is here to stay for a very long time

  • @SebaKerckhof
    @SebaKerckhof 11 дней назад

    Remember how the promise of react was that functional components would be easy to reason about. It's one giant clusterfuck of a footgun by now.

  • @AsifMushtaq-k6b
    @AsifMushtaq-k6b 26 дней назад +6

    How long we are going to learn React? Is it's a matrix lol

  • @ryandsouza2962
    @ryandsouza2962 24 дня назад +1

    Amazing vid! This make s my decision of going with Astro and HTMX even stronger

  • @Grow.YT.Views.246
    @Grow.YT.Views.246 26 дней назад +1

    So much value!

  • @adityatiwari811
    @adityatiwari811 11 дней назад

    Good one. Thanks!

  • @derekpowersblight
    @derekpowersblight 26 дней назад +1

    I "Promise All" these work :P

  • @MrJettann
    @MrJettann 26 дней назад +1

    Awesome as always, Jack! Will your course be available any soon?)

    • @jherr
      @jherr  26 дней назад +1

      I certainly hope so.

  • @mueslirieger
    @mueslirieger 26 дней назад +1

    Man, I love your videos. Even though I'm currently more focused on development with Nuxt, I usually use your videos to keep up to date on React stuff, and I gotta say, your videos never disappoint. They are so informative and your presentation is neither distracting nor boring. This is simply the best React content there is imo

  • @elraito
    @elraito 26 дней назад +2

    I cant explain exatcly why but with all these intrinsic sort of "rules" you need to know just keep muddying react. When thet introduced hooks i wasnt able to see why it was necessary at first but started to like the patterns on time. I hope the same is happening now when it finally "clicks" i eill actually like the new rules

  • @peterjatek365
    @peterjatek365 16 дней назад

    Thnx for the video, you are great!
    What happened, why don't you use typescript in your projects currently?

    • @jherr
      @jherr  16 дней назад +1

      Oh, for that video, the types weren't out for use, etc. I normally do.

  • @thisweekinreact
    @thisweekinreact 26 дней назад +1

    Interesting and surprising behavior 😮
    Couldn't this be considered a React bug or temporary limutation?
    Is there a reason for reexecuting the useState init? Looks like the previous/initial result could be reused.

    • @jherr
      @jherr  26 дней назад +2

      I believe this is a temporary limitation meant to be a cautious step to supporting this async behavior that is still 100% compatible with the original behavior. Basically if you use the new feature, then you flip into this new mode, and if you don't then you stay in the original mode with 100% compatibility. And the downside is this state reset, which, if you code your components correctly with hoisted state, then you won't even see it.

    • @thisweekinreact
      @thisweekinreact 26 дней назад

      ​@@jherr I'm not sure about the "mode flipping" framing.
      I may be wrong but to me it's not really a mode, but just that calling "use(unresolvedPromise)" would throw a promise
      If you use a Suspense-enabled lib like React-Query, wouldn't this give you the same behavior?

    • @jherr
      @jherr  26 дней назад +2

      FWIW, I did talk to folks on the React team and there is a "mode". Yes, use throws, and that stops the execution. But if "throw === state reset" then we would expect that if you have useState, use, useState, use, that you'd get two state resets, but you don't. You only get one, and only one. And once you are in the "use" mode you are good from then on.
      Using RQ kind of fixes this for the same reason that hoisting fixes this since the RQ cache is an implicit hoist. RQ will give you back the same cached promise even after the state reset. The issue here is with creating the promise in the component as state, either with useState or useReducer. If you create it in the initializer then you will genuinely get a fresh new promise because the original state is discarded.
      The easiest "fix" for use(fetch) is to do use(useState(() => fetch())[]). What this video shows it that you'll get a double fetch if you do that, and why. :)
      Thanks for watching and supporting first party original content. I really appreciate what you do.

    • @thisweekinreact
      @thisweekinreact 25 дней назад

      @@jherr Sure 👍 thanks for producing it in the first place
      Thanks, didn't know about that mode!

    • @jherr
      @jherr  25 дней назад +1

      @@thisweekinreact From the conversation, it's a transient implementation detail that may or may not be there in the future. Just write your code correctly (i.e. hoisting either to a parent component or a query cache (RQ)) and you probably won't even notice it.

  • @bigmistqke
    @bigmistqke 24 дня назад +1

    I don't really understand the design decision behind why use is sequential. Why wouldn't they simply collect the promises during mount and then resolve them in parallel 🤔I suppose they can't attach it to the fiber because it's not really a hook? But then maybe keep the hook rules for use? Add a dependency array if u want to conditionally fetch, tanstack query style?

    • @jherr
      @jherr  22 дня назад +1

      That’s just a natural outcome of JS executing this component function and use throwing a promise until the promise is resolved. That throw halts the execution of the function as any throw does. If they didn’t throw then 1) react would know how to track the promise that needs to be resolved and 2) if you had multiple uses with logic in-between that logic would need to be safe to handle undefined values while the promise is still in flight.

  • @aurorasofie
    @aurorasofie 25 дней назад +1

    Hi Jack, what is the case where we would have to worry about this? Considering the use hook is meant to be used with promises created on the server.

    • @jherr
      @jherr  25 дней назад

      Where do you get that "meant to be used with promises created on the server"? Looking at the documentation, for RSC client components using RSC with suspense and client component using use is preferred, but that doesn't mean that `use` is exclusively for SSR setups. It works perfectly fine in SPAs, and Suspense is just as useful in the SPA context as it is in SSR applications.

    • @aurorasofie
      @aurorasofie 25 дней назад

      @@jherr in the React docs the second use case of “use” other than reading a context is under “streaming data from server to client”, I don’t see any example there of other use cases. What benefits do you get from using it in a SPA?

    • @jherr
      @jherr  25 дней назад

      @@aurorasofie That example is just showing that in the context for RSC -> Client component rendering that you'll get a streaming behavior where the connection is held open until the data is available and then when it is the hole in the VDOM is filled with the new component. That doesn't mean that its the only thing you should use it for. In the case of a SPA it's a very elegant way of managing the request flow showing placeholders (probably skeletons) until the data is available.

    • @aurorasofie
      @aurorasofie 25 дней назад

      @@jherr In practice, would that mean moving your data fetching one level up from the component to suspend it? Like with RSC and client components. Wouldn’t react query still be a better option?

    • @jherr
      @jherr  25 дней назад

      ​@@aurorasofie You can think of it that way, you can also think of it as moving the rendering down one layer and separating the concerns of data fetching and loading state/data state rendering.
      You can think about RQ in the same light; what advantages, in the simplest case, does RQ offer over this? Sure I can think of a lot of cases where the extra functionality in RQ is not modeled here, but in the case of simply fetching data for rendering, there is no inherent advantage to using RQ, you're just adding more JS to your app.
      The React team has added first class support into the framework for managing promises. That means that we can, in many cases, remove the code that was previously handling promise management in favor of the new model provided by the framework itself.

  • @DihCpsPsy
    @DihCpsPsy 26 дней назад

    Is there a date to when Pro Nextjs will be available?

    • @jherr
      @jherr  26 дней назад +1

      Unfortunately I don't have one, soon I hope. I put a ton of work into it and I can't wait for y'all to see it.

    • @DihCpsPsy
      @DihCpsPsy 26 дней назад

      @@jherr , yeah that will be very interesting, looking forward to it!

  • @jazsouf
    @jazsouf 25 дней назад

    Also, if you want to extract the use() logic in a separate hook that you name usePromiseData for example, you won't be able to call it conditionally but use() can. You should call your function unwrapPromiseData instead.
    I think this API should have been called something other than use, since it doesn't follow the rules of hooks per se.

    • @jherr
      @jherr  24 дня назад

      I think you can call it conditionally, though I haven't tried it. React doesn't know you are calling a hook function versus any other function. The linter might yell at you though. But it'll still work at runtime. And yes, you could avoid the linter issue by calling it something other than `use*()`.

    • @jazsouf
      @jazsouf 24 дня назад

      @@jherr My bad, you're right yes it's a linter issue.
      Still, I think calling it 'use' isn't ideal.
      I love the new react 19 features, but the naming of some of the new stuff is confusing at first. ('use client' for 'use client portal', 'use server' for 'use server function', 'cache' for 'dedupe' and 'use' for 'unwrap'...)

    • @jherr
      @jherr  24 дня назад

      @@jazsouf 🤷‍♂ naming stuff is hard. I don't envy them that.

  • @SteveCrozier
    @SteveCrozier 20 дней назад

    What do you mean by "you can't have async components on the client"? I assume I'm missing some context here.

    • @jherr
      @jherr  20 дней назад

      Component functions that execute on the client can't be `async` functions. React Server Components (RSCs) can be async functions. But they can only run in RSC contexts (primarily on the server.)

    • @SteveCrozier
      @SteveCrozier 20 дней назад

      @@jherr Oh, right. But you can put that fetch in a useEffect hook. Is there a reason not to do that? We do that all the time to fetch data for a component.

    • @jherr
      @jherr  20 дней назад

      @@SteveCrozier Of course. But this just gives an alternative workflow to that. You can put that fetch promise in useState, then send the promise to a sub-component, wrapped in a Suspense, and the sub-component can then use the `use` hook to grab the data once it's available. And then you can use use the `fallback` property on the Suspense to put in whatever placeholders you want.
      And that code is roughly symmetric with what you'd do if you were using RSCs. Though in the case of an RSC parent component you would just have the fetch with no useState.

    • @SteveCrozier
      @SteveCrozier 20 дней назад

      @@jherr Hmm. Not sure I understand the advantage to what seems like a lot of extra, fairly complex code. I would just use some simple conditional logic to insert placeholders. But I haven't dug into Suspense much, tbh. I may well be missing the point.

    • @jherr
      @jherr  20 дней назад +1

      @@SteveCrozier I wouldn't discount it out of hand. The React team has created a great platform. It's worth keeping an eye on what they are adding to the platform.

  • @evolopterus
    @evolopterus 26 дней назад

    Is this as big of a deal as the previous hooks drama? useEffect has not been particularly loved by many, and it seems there were too many gotchas and ways things went wrong. Is this the same deal all over again? It's important that React steers towards a direction of being simple and predictable, with little room for screwups that are not immediately obvious.

    • @jherr
      @jherr  26 дней назад

      No. This is just some small transient behavior that may change that if you were to encounter it then you might be confused. So this is letting you know that it’s out there.

  • @georgios_georgiou
    @georgios_georgiou 27 дней назад +1

    dang ! second here

  • @xdevchris
    @xdevchris 25 дней назад +1

    1+2+3 = 7 ?? maths aren't mathing dude

    • @jherr
      @jherr  25 дней назад +1

      I reference that a little after, and explain why it is 7 and not 6 and then get it to be 6.

    • @xdevchris
      @xdevchris 25 дней назад

      @@jherr yeah I know, just wanted to do this joke :3 really useful content

    • @jherr
      @jherr  25 дней назад

      @@xdevchris Hahahah, no worries. Glad you like it.

  • @MrZiyak99
    @MrZiyak99 27 дней назад

    const [namePromise] = useState(() => {
    return fetch(`/name.json?${Math.random()}`)
    .then((res) => res.json())
    .then((data) => data.name);
    });
    why this vs
    const namePromise = fetch(`/name.json?${Math.random()}`)
    .then((res) => res.json())
    .then((data) => data.name);
    });

    • @user-iv7ci3hp2u
      @user-iv7ci3hp2u 27 дней назад

      Because in the second variant it will fire a new request each time the component rerenders, but in the first it will be fired only once as it's passed to useState as default value function which is executed once

    • @jherr
      @jherr  27 дней назад

      If you just have the bare fetch then `namePromise` will just resolve and ... nothing. You have to set some state in the component to get the component to re-render. So you'd have to have this:
      fetch(`/name.json?${Math.random()}`)
      .then((res) => res.json())
      .then((data) => setName(data.name));
      });
      Which is problematic because when the component re-renders it will start the fetch again. Which will in turn set the name, which would normally re-render, but in this case because the name is just a string and the string is the same between the first and second fetch, then you won't get an infinite loop. But that's only because the example is super simple.

    • @MrZiyak99
      @MrZiyak99 26 дней назад

      @@jherr thanks. i understood the rendering infinite loop part but I' m now confused on why a regular named promise won't do anything after its resolved. won't the use hook take care of that? or does it only take care of it when inside a useState

    • @jherr
      @jherr  26 дней назад

      @@MrZiyak99 Your second example is just a bare fetch. There is no use. Neither of your examples in your comment shows a use. Please give me an example that shows a use and I'll tell you why it does, or doesn't, work as expected.

    • @MrZiyak99
      @MrZiyak99 26 дней назад

      @@user-iv7ci3hp2u i think there is more to it check @jherr comment cause otherwise feel like we could also do
      const namePromise = React.useMemo(() => {
      return fetch(`/name.json?${Math.random()}`)
      .then((res) => res.json())
      .then((data) => data.name);
      }, []);
      but i feel likke it wont work from what i can understand from @jherr comment

  • @YourAdamDriver
    @YourAdamDriver 25 дней назад

    For me it looks like an awful api decision by the react team to create this `use` hook. It has way too many responsibilities

    • @jherr
      @jherr  25 дней назад

      Adam, you should use your force powers to look inside use to see that it's one and only responsibility is to monitor a promise.

    • @YourAdamDriver
      @YourAdamDriver 19 дней назад

      @@jherr It has at least two: readContext and useThenable. Anyway, I don't find it convenient to use for promises. And we already have another hook to get context values

  • @SeanCTTR
    @SeanCTTR 22 дня назад

    I don’t understand this at all. This sounds much worse than every alternative.