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

แชร์
ฝัง
  • เผยแพร่เมื่อ 6 ก.ย. 2024

ความคิดเห็น • 87

  • @jazzmaster89
    @jazzmaster89 2 หลายเดือนก่อน +24

    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 2 หลายเดือนก่อน +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  หลายเดือนก่อน +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 หลายเดือนก่อน

      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  หลายเดือนก่อน

      @@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 2 หลายเดือนก่อน +10

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

  • @rodrigorb2630
    @rodrigorb2630 หลายเดือนก่อน +2

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

  • @odra873
    @odra873 2 หลายเดือนก่อน +11

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

  • @AsifMushtaq-k6b
    @AsifMushtaq-k6b หลายเดือนก่อน +6

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

  • @o_glethorpe
    @o_glethorpe 2 หลายเดือนก่อน +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  2 หลายเดือนก่อน +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 2 หลายเดือนก่อน +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 หลายเดือนก่อน

      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  หลายเดือนก่อน +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 หลายเดือนก่อน

      @@jherr Thank's a lot for clarification!

  • @blizzy78
    @blizzy78 2 หลายเดือนก่อน +2

    I love these in the weeds videos, keep em coming

  • @mertdr
    @mertdr 2 หลายเดือนก่อน +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  2 หลายเดือนก่อน +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 2 หลายเดือนก่อน +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  2 หลายเดือนก่อน +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.

  • @versaleyoutubevanced8647
    @versaleyoutubevanced8647 2 หลายเดือนก่อน +6

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

  • @ryandsouza2962
    @ryandsouza2962 หลายเดือนก่อน +1

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

  • @bigmistqke
    @bigmistqke หลายเดือนก่อน +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  หลายเดือนก่อน +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.

  • @SebaKerckhof
    @SebaKerckhof หลายเดือนก่อน

    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.

  • @mueslirieger
    @mueslirieger หลายเดือนก่อน +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

  • @Grow.YT.Views.246
    @Grow.YT.Views.246 2 หลายเดือนก่อน +1

    So much value!

  • @adityatiwari811
    @adityatiwari811 หลายเดือนก่อน

    Good one. Thanks!

  • @derekpowersblight
    @derekpowersblight 2 หลายเดือนก่อน +1

    I "Promise All" these work :P

  • @elraito
    @elraito 2 หลายเดือนก่อน +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

  • @thisweekinreact
    @thisweekinreact หลายเดือนก่อน +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  หลายเดือนก่อน +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 หลายเดือนก่อน

      ​@@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  หลายเดือนก่อน +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 หลายเดือนก่อน

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

    • @jherr
      @jherr  หลายเดือนก่อน +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.

  • @aurorasofie
    @aurorasofie หลายเดือนก่อน +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  หลายเดือนก่อน

      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 หลายเดือนก่อน

      @@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  หลายเดือนก่อน

      @@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 หลายเดือนก่อน

      @@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  หลายเดือนก่อน

      ​@@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.

  • @fexxix
    @fexxix หลายเดือนก่อน

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

  • @MrJettann
    @MrJettann 2 หลายเดือนก่อน +1

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

    • @jherr
      @jherr  2 หลายเดือนก่อน +1

      I certainly hope so.

  • @peterjatek365
    @peterjatek365 หลายเดือนก่อน

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

    • @jherr
      @jherr  หลายเดือนก่อน +1

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

  • @readDocs
    @readDocs หลายเดือนก่อน

    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  หลายเดือนก่อน

      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*()`.

    • @readDocs
      @readDocs หลายเดือนก่อน

      @@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  หลายเดือนก่อน

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

  • @SteveCrozier
    @SteveCrozier หลายเดือนก่อน

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

    • @jherr
      @jherr  หลายเดือนก่อน

      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 หลายเดือนก่อน

      @@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  หลายเดือนก่อน

      @@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 หลายเดือนก่อน

      @@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  หลายเดือนก่อน +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.

  • @DihCpsPsy
    @DihCpsPsy 2 หลายเดือนก่อน

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

    • @jherr
      @jherr  2 หลายเดือนก่อน +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 หลายเดือนก่อน

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

  • @evolopterus
    @evolopterus หลายเดือนก่อน

    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  หลายเดือนก่อน

      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.

  • @xdevchris
    @xdevchris หลายเดือนก่อน +1

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

    • @jherr
      @jherr  หลายเดือนก่อน +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 หลายเดือนก่อน

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

    • @jherr
      @jherr  หลายเดือนก่อน

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

  • @georgios_georgiou
    @georgios_georgiou 2 หลายเดือนก่อน +1

    dang ! second here

  • @MrZiyak99
    @MrZiyak99 2 หลายเดือนก่อน

    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 2 หลายเดือนก่อน

      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  2 หลายเดือนก่อน

      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 2 หลายเดือนก่อน

      @@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  2 หลายเดือนก่อน

      @@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 2 หลายเดือนก่อน

      @@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

  • @SeanCTTR
    @SeanCTTR หลายเดือนก่อน

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

  • @YourAdamDriver
    @YourAdamDriver หลายเดือนก่อน

    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  หลายเดือนก่อน

      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 หลายเดือนก่อน

      @@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