Frontend

Make use of `use` in React – a new hook is coming | Frontend Weekly vol. 109

The team behind React has unveiled a new hook called `use`. This inconspicuous creation could forever change the way we get data into our applications.

Article cover

1. First class support for Promises in React

Every React RFC (Request For Comments) arouses a lot of interest. This time was no different, as the newly published RFC introduces native support for Promises. For some, the proposed solution is overcomplicated. Others, glorify the RFC to the level of “the last missing React functionality.” As is usually the case, the truth is somewhere in the middle, and today we will take a closer look at the proposed solutions.

Client Side Components

Looking at the title of the new RFC you may wonder what it’s really about. After all, React is fully compatible with Promises and probably most of you have written something like this:

async function fetchNote(id) {
  const data = await fetchNoteFromBackend(id);
  return data.json();
}

function Note({ id }) {
  const [note, setNote] = useState(null);
  
  useEffect(() => {
    fetchNote(id).then(it => setNote(it))
  }, [id])

  if (note === null) {
    return <h2> Loading... </h2>
  } 

  return (     
    <div>
      <h2>{note.title}</h2>
      <section>{note.body}</section>
    </div>
  );
}

Libraries such as React Query provide a slightly nicer API for the problem. Nevertheless, we still have to manually handle the various possible states of Promise.

async function fetchNote(id) {
  const data = await fetchNoteFromBackend(id);
  return data.json();
}

function Note({ id }) {
  const query = useQuery(`note-{id}`, fetchNote)

  if (query.isLoading) {
    return <h2>Loading...</h2>;
  }

  return (
    <div>
      <h2>{query.data.title}</h2>
      <section>{query.data.body}</section>
    </div>
  );
}

The solution that the team behind React proposes is to introduce a new hook called use. Using it, it is possible to read the value of a fulfilled Promise. Error handling and loading will be delegated to the nearest Suspense. This architecture allows grouping components into contexts, which are ready to be rendered only when all components load data.

async function fetchNote(id) {
  const data = await fetchNoteFromBackend(id, { useCache: true });
  return data.json();
}

function Note({ id }) {
  const note = use(fetchNote(id));
  return (
    <div>
      <h2>{note.title}</h2>
      <section>{note.body}</section>
    </div>
  );
}

function App() {
  return (
    {/* Loading will be displayed until all Note components fetch necessary data */}
    <Suspense fallback={<h2>Loading…</h2>}>
      <Note id="1" />
      <Note id="2" />
      <Note id="3" />
    </Suspense>
  );
}

It is worth noting that if the reference to Promise changes between renders, the fallback from the Suspense component will be displayed again. The only exception to this rule is when you pass fulfilled Promise to use. In this situation, React will immediately display the rendered component. This means that to avoid unwanted blinking, it is necessary to add cache support. We can either rely on external libraries for this or simply implement it ourselves with useMemo. Fortunately, the team behind the RFC promises that they will also soon present a more verbose solution for caching promises.

async function fetchNote(id) {
  const data = await fetchNoteFromBackend(id);
  return data.json();
}

function Note({ id }) {
  const promise = useMemo(() => fetchNote(id), [id]);
  const note = use(promise);
  return (
    <div>
      <h2>{note.title}</h2>
      <section>{note.body}</section>
    </div>
  );
}

If you think it’s gotten complicated, we’re just getting started. The use will be the first and only hook that can be used inside an if statement or a loop. 

function Note({ id, shouldIncludeAuthor }) {
  const note = use(fetchNote(id));

  let byLine = null;
  if (shouldIncludeAuthor) {
    const author = use(fetchNoteAuthor(note.authorId));
    byLine = <h2>{author.displayName}</h2>;
  }

  return (
    <div>
      <h2>{note.title}</h2>
      {byLine}
      <section>{note.body}</section>
    </div>
  );
}

There are also plans to add a Usable interface in the future. This interface will allow us to pass whatever we like into the hook. The RFC authors cite context as an example. If their plans come into effect, the API will include both use(Context) and useContext(Context). You will be able to call the first one in conditional statements, but not the second one. 

// ✅ This will work fine
function Author({ display }) {
  if (display) {
    const authors = use(AuthorsContext);
    return <h2>{authors[note.authorId]}</h2>;
  } else {
    return null
  }

// 🛑 This will not work
function Author({ display }) {
  if (display) {
    const authors = useContext(AuthorsContext);
    return <h2>{authors[note.authorId]}</h2>;
  } else {
    return null
  }
}

Server Side Components

The second novelty presented in the RFC concerns React Server Component. Since they are an experimental part of the library, let’s pause for a moment to remind ourselves what they actually are. 

The concept was first presented in late 2020. Server Components allow rendering part of the component tree on the client side and part on the server side. Components rendered on the server side are sent as an HTML stream. This strategy slim down the package sent to the client a lot. Also, this architecture allows you to access typically server-side functions (for example, the database or file system) directly from the component code.

import fs from 'react-fs';
import db from 'db.server';

function FileNote({ id }) {
  const note = JSON.parse(fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}

function DatabaseNote({ id }) {
  const note = db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

If you are looking for resources to better understand the Server Components concept, I recommend the original 2020 feature presentation. If you want to dig even deeper, I refer you to the long and extensive RFC.

A big shortcoming of Server Components so far has been friendly support for asynchronous actions, so typical of server operations. The newly presented RFC addresses this problem by allowing the use of native async/await syntax. The only caveat is that components using async/await cannot use hooks.

async function Note({ id, isEditing }) {
  const note = await db.posts.get(id);
  return (
    <div>
      <h2>{note.title}</h2>
      <section>{note.body}</section>
    </div>
  );
}

Whether you like the proposed changes or not, let’s remember not to go to extremes. It will most likely be a long time before the RFC is implemented, and we can rather expect a few changes to the specification. Nor can we reject the scenario in which use will share the fate of useEvent and, despite initial optimism, will never see the light of day in the form described here today.

Sources

https://github.com/reactjs/rfcs/pull/229
https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
https://www.youtube.com/watch?v=ytXM05PVcFU

Discover more IT content selected for you
In Vived, you will find articles handpicked by devs. Download the app and read the good stuff!

phone newsletter image

2. Lerna comes back to life

Lerna is a monorepo management solution that for a time was a monopolist in the JavaScript category. However, over the past few years, the project has encountered considerable turbulence. In mid-2020, an Issue appeared in the Lerna repository saying that the tool was no longer actively supported. Looking at the history of commits, it’s hard to disagree – most of the changes concern dependency updates and critical vulnerability fixes. As a result of lengthy discussions, a note was added to the readme announcing the end of support for the project. 

Over the past two years, new alternatives have sprouted up in Lerna’s place. Both yarn and npm have prepared native support for monorepo in the form of workspaces. There was also no shortage of tools offering support for more advanced functionality in the form of Nx and TurboRepo. Nx overtook Lerna in terms of npm downloads in April this year. Moments after this fact, something completely unexpected happened – the company behind the development of Nx announced that it was going to handle the development of Lerna as well. 

Since the announcement of the project’s acquisition, two major versions of the tool have already hit our hands. The first, back in May of this year, bumped up key dependencies and removed false warnings thrown at the console. The second, released last week, is the first version in more than two years to introduce new features. Among them are

  • Enable package caching support with nx by default for all modules
  • Adding remote cache support along the lines of that known from nx
  • Ability to define dependencies between modules, using the configuration placed in the `nx.json` file
  • pnpm support
  • Dynamic terminal output, very similar to the nx one
  • Dedicated VSCode extension

As you can see, quite a few of the new functionalities are created along the lines of nx or even add integration with nx. There’s nothing wrong with that, but I personally regret a bit that Lerna is not developing new functionality in parallel with nx. After all, greater diversity has always driven innovation. Fortunately, we still have TurboRepo on the market, so we don’t have to worry about stagnation in the monorepo area for now.

Sources

https://blog.nrwl.io/lerna-reborn-whats-new-in-v6-10aec6e9091c