Skip to content
Hunter Miller

AUTHOR

Hunter Miller

Senior Software Engineer

Select...
Select...
React 18: Concurrency and Streaming SSR cover image

React 18: Concurrency and Streaming SSR

React 18 The React team just announced React 18 alpha, along with their plans for release, earlier this month. Included in that announcement is the creation of a working group to help prepare the community for the new features, nearly all of which can be gradually adopted. Concurrency (Concurrent mode begone!) So we finally have an answer to what we've been wondering about for the past three years! React 18 will ship with a new concurrency model, but not a completely separate concurrency "mode". Concurrency will come from a new set of features, and just happen automatically when those features are used, so it is completely optional. createRoot will now be the way that we initialize the root of our React applications going forward as you can see here: ` This new createRoot also enables all the new concurrent features- a subtle distinction from a "mode" 😉. This model allows you to render work and events to be interleaved together, and allows you to assign priority to various kinds of updates. This gives us better ways of keeping UI fluid and performant. Priority To achieve concurrency, React now uses a cooperative multitasking model which runs a single thread that can be interrupted based on priority. Interruptions happen in between rendering various components. Since the time it takes to render a component is usually very small, it provides a very granular way to interrupt when high priority work comes through. If you'd like to read more in depth, check out this topic in the working group. startTransition One of the new concurrent features is startTransition, which allows us to specify updates that might be lower priority- aka "transition" updates. Specifying updates as "transition", enables urgent updates to interrupt them, allowing interactions to be more performant. To do this, we'll use a new API: ` Urgent vs Transition Updates Urgent actions are things like clicking, hovering, scrolling, typing, or any other action where the user expect things to happen instantly as they do natively in the browser. Transition updates are more "React-centric" things like handling and managing UI state. Currently, all updates in React 18 and below are considered urgent. The way this is worded seems to imply a future where all updates are considered transitions, and we only need to specify urgent updates. I think it was done this way to make the adoption even more gradual, because I think realistically, most updates should be transitional, not urgent. However, people need time to get used to the idea of specifying priority before everything actually gets de-prioritized. Automatic Update Batching A small but significant change is that now all updates are automatically batched. You may have noticed, when building React apps, that any time you put multiple setState calls next to each other in something like an event handler, React combines them into one update so that only one render occurs. However, you may have also noticed that this does not occur in things like callbacks, setTimeout, Promises, etc. ` flushSync However, maybe you were relying upon that non-batching behavior! Well, there's a new API to opt-out of batching (although the team says this should be uncommon): ` Streaming Server Render Probably one of the most impactful changes in this set is now the ability for server-side rendering or SSR to be streamed to the browser, enabling partial page sections to be rendered before the entire page is ready. This should speed up the time it takes for the user to see anything on the page. Streaming + Selective Hydration What does this mean exactly? So first, current SSR efforts do the entire render of the HTML page, and wait until it's completely finished before sending it to the browser. The new method allows the page to be broken up into chunks using . Now, the server can start streaming HTML as soon as one of these chunks is ready. Even if that HTML gets to the browser faster, it still needs to be hydrated. Previously, this was an all-or-nothing operation, requiring the entire page's JavaScript to be downloaded. Now, we can use a combination of and React.lazy() to achieve partial hydration as well. If you'd like to see an example of how the server handles this, the React team put together a nice demo: Event Replay and Priority If that wasn't enough already, the team also found a way to prioritize hydration for user interactive sections. React will now record interactions with various parts of the page, and prioritize hydration based on what has been interacted with. This is really elegant because it lets things that the user cares about render as quickly as possible. 👏👏👏👏 Suspense is Now Very Important These new features are all enabled entirely by the component. Wrapping parts of the page that may take longer to load enables their execution to be deferred while the rest of the page gets rendered, streamed, and hydrated, enabling things to be faster and quicker to interact with! If you'd like to see an in-depth explanation, check out this topic in the working group complete with diagrams. Conclusion This is an extremely exciting update with lots of promise towards better performing apps and faster load times! Increased control over what gets rendered and hydrated first will lead to much better user experiences, especially on devices that struggle to load JS efficiently. I'm very glad we will finally get an answer on concurrency "mode" too! 😂 If you're excited to try these new changes, go ahead and install the alpha: ` The React team is looking for feedback through the working group, so feel free to participate in discussions there. I have to give lots of credit towards @swyx, who did a lot of investigation work to point out key features from the release in this thread. Check it out! Thanks!...

What Can You Do with Tailwind 2? cover image

What Can You Do with Tailwind 2?

Tailwind CSS released their new 2.0 version November 18th, 2020 with a spectacular launch trailer and new website redesign. The new version includes a plethora of features including new color palette, ring and form utilities, and dark mode, among others!...

Why You Should Use React Query or SWR cover image

Why You Should Use React Query or SWR

Why You Should Use React Query or SWR for External Data Most of us out here in React-land are out building our apps with the shiny new Hooks API and slinging requests to external APIs like nobody's business. Those of us new to hooks may have started creating hooks that look like this simplified example: ` The Problem However, each time a hook is called, that instance is unique to the component it was called in, so there's a few issues we'll run into: 1. Updating the above query in one instance won't update it for the other instances. 2. If we have three components using a hook that makes an API request, we will get at least one request per component. 3. If we have multiple requests in the air, and we try to store them in a global store or have the hook hold the state, we will end up with state out of sync or subsequent requests overwriting each other. One way to solve this is to leave the request out of hooks and only call it on mount in a component you can guarantee will be singular (aka a singleton component, like a page/route perhaps). Depending on how that data is used, this can sometimes be very tricky to do. Potential Solutions So what can we do? There are a few options: 1. Make sure the data returned from the API goes in context or some kind of global state management, accepting the multiple requests (potentially overexerting our server's API) 2. Doing the above + use a library like react-singleton-hook, ensure there's only a single component with the useEffect doing the API call, or similar to prevent multiple requests. 3. Implement some kind of way to cache the data (while still being able to invalidate that as necessary) so that we pull from the cache first 4. Use React Query or SWR The Real Solution The real solution here is to use option 4. Both libraries have done really sophisticated work to solve these problems to prevent you from having to do it yourself. Caching is extremely tricky to get right and can have pretty dire consequences if done poorly, leading to your app partially or completely breaking. Other Problems They Solve Here are a few examples of other problems these libraries can solve with code examples for each. _Code examples are using React Query but are nearly the same with SWR._ Window Focus Refetching One big issue that we encounter with Javascript heavy sites and apps is that a user may be on one tab/window messing with data and then switch to another of the same app. The problem here is that if we aren't keeping our data fresh, these can fall out of sync. Both libraries solve this by refetching data once the window has focus again. If you don't need that or can't have that behavior, you can simply disable as an option. ` Request Retry, Revalidation and Polling Sometimes requests error temporarily, it happens. Both libraries answer this problem by allowing configured automatic retries, so anytime they encounter an error, it will retry the specified amount of times until finally throwing an error. Additionally, you can use either to poll an endpoint constantly by just setting their refetch/refresh interval to a number in milliseconds. Retry Example ` Polling Example ` Mutation with Optimistic Updates Let's say you have a list of users and you want to update the information of one of those users, a pretty standard operation. Most people are pretty content with seeing a loader to indicate the server is working to update that user, and waiting for it to finish before seeing the updated list of users. However, if we know what the updated list of users will look like locally (because the user just made the update themselves), do we really even need to show a loader? No, both libraries allow you to do mutations on your cached data that will update locally immediately and start the update to the server in the background. They will also make sure that data gets refetched/validated to be the same once it returns from the server, and if not, the returned data will fall into place. Imagine we have a page where the user gets to edit their information. First, we need to fetch that information from the backend. ` Next we need to setup a function to allow us to update the user's data once they submit the form. ` If we want to have our local data update the UI optimistically, we have to add some options to the mutation. The onMutate here will shove the data locally into the cache firing before the actual update so our UI won't show a loader. The return value is used in case of an error, and we need to reset to our previous state. ` If we're updating optimistically, we need to be able to handle errors and also make sure that the server returns the same data. So we add two more hooks into our mutation's options. onError will use the data returned from onMutate so that we can reset to the previous state. onSettled makes sure we refetch the same data from the server so that we don't end up out of sync. ` Prefetching and Background Fetching If you have an idea about some data that the user is about to need, you can use these libraries to prefetch that data. By the time the user gets to it, the data is already loaded making the transition instant. This can really make your apps feel snappier. ` On a similar note, if the user already has received some data, but it's time to refresh, the libraries will fetch new data behind the scenes and replace the old data if and only if that data is different. This prevents showing the user a loading indicator, only notifying them if there's something new which is a much better user experience. Imagine we have a Twitter-style feed component that constantly is refetching for new posts ` We can notify users that the data is upgrading in the background by listening for isFetching to be true which will fire even if cache data is present. ` If we have no data in the cache at all and the query is fetching data, we can listen for isLoading to be true and show some kind of loading indicator. Finally, if isSuccess is true and we received data, we can display the posts themselves. ` Feature Comparison React Query's creator did a great job building out a feature table comparison for React Query, SWR, and Apollo for you to see what features are available. One big feature that I'd like to call out that React Query has over SWR is it's own set of dev tools which are really helpful for debugging misbehaving queries. Conclusion Over my time as a developer, I've tried to solve these problems myself, and had I had a library like React Query or SWR, I would have saved a ton of time. These problems can be really tricky to solve and the solutions can inject subtle bugs into your app that are difficult or time-consuming to debug. Luckily, we have open source and these people were generous to offer their own robust solutions for us to use. If you'd like to see more about the problems these libraries solve and the efforts needed to solve them, Tanner Linsley did a great walkthrough of what he was experiencing and how he solved it. You can watch that walkthrough here: Overall I think these libraries are great additions to the ecosystem and will help us all write better software. I'd like to see other frameworks come out with similar libraries, because the concepts here are pretty universal. I hope you found this helpful and let us know any unique strategies you have when using these libraries! PS. What about GraphQL? 😂 Well, a lot of the GraphQL libraries out there actually built in a lot of these concepts from the get go, so if you're using something like Apollo or Urql, you probably are already getting these benefits. However, both libraries are compatible with anything that returns a promise, so if your particular favorite GQL library doesn't have these features, try putting React Query or SWR in front of it. 😁...