Skip to content

Instantly share code, notes, and snippets.

@bholmesdev
Created October 15, 2022 18:22
Show Gist options
  • Save bholmesdev/f033a992d318938af353ac1619835ba0 to your computer and use it in GitHub Desktop.
Save bholmesdev/f033a992d318938af353ac1619835ba0 to your computer and use it in GitHub Desktop.
React async / await RFC notes

React async-await RFC

What I know coming in

React server components (initial proposal) was a so-so DX that solved an important problem: I want to render a React component on the server only, with APIs to easily fetch data for rendering.

  • Pulls on islands architecture concepts (hi Astro.build!)
  • Improves full-stack ergonomics

Key takeaways

Proposal streamlines underpinnings of React Query, NextJS, and Remix, but does not replace them.

Server components, simplified

  • server components will just be async components. No cognitive overhead, just async / await:
async function Note({id, isEditing}) {
  const note = await db.posts.get(id);
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {isEditing ? <NoteEditor note={note} /> : null}
    </div>
  );
}
  • why this was missing: didn't want server / client components to feel different
  • why this was added: writing React adapters for every server-side API (a majority built around async / await) isn't worth the cost
  • Makes server components easy to identify. If it says async function, it's intended for the server
    • ❓ Kinda weird assumption if async is supported on the client at some point (see last section)
    • ❓ Async != server in most people's minds

We've since changed our mind. We now believe that the benefits of async/await in Server Components outweigh the downsides of having a different API (use) on the client.

Client-side promises: use function

React is not bringing async / await to client components due to technical footguns (see last section). Instead, you can "unwrap" promises and block rendering with use:

// `use` inside of a React component or Hook...
const data = use(promise);
// ...roughly equates to `await` in an async function
const data = await promise;

Adapts rules of promises to hooks:

  • use will only work inside components and hook functions (like traditional hooks)
  • use will work inside conditionals and loops (like await, unlike traditional hooks)
  • use will block rendering until promise is resolved. Will bubble up to the suspense boundary during loading to render any fallbacks, and will unblock any UI when ready
  • Can use for conditional fetching too (again, unlike traditional hooks like useQuery)
function Note({id, shouldIncludeAuthor}) {
  const note = use(fetchNote(id));
  let byline = null;
  if (shouldIncludeAuthor) {
	// only make expensive fetch when author is needed
    const author = use(fetchNoteAuthor(note.authorId));
    byline = <h2>{author.displayName}</h2>;
  }
  return (
    <div>
      <h1>{note.title}</h1>
      {byline}
      <section>{note.body}</section>
    </div>
  );
}

Will not ship without cache

Without a cache, use would fetch + resolve on every render (at least w/o a useEffect presumably). We need a useQuery-like caching strat

Missing from present proposal, but will add a cache helper to wrap fetch calls 👀

const fetchNote = cache(async (id) => {
  const response = await fetch(`/api/notes/${id}`);
  return await response.json();
} /* unknown: caching options */);

function Note({id}) {
  // The `fetchNote` call returns the same promise every time until a new id
  // is passed, or until the cache is refreshed.
  const note = use(fetchNote(id));
  ...

Why use over promises?

  • if components were async, it would be too easy to invalidate a component prop and re-render; performance
    • It's not so much that it introduces new performance caveats, but it makes all the performance caveats described above much more likely

  • flagging with use could pave way for auto-memoizing compiler
  • still async/ await is technically possible on the client
  • will warn async components with dev server error, but will not fail to build (huh??)
    • confirmed in PR comments
    • React wants NextJS to play with this idea internally before they start throwing compiler errors. Sounds like they're unsure of best practices right now
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment