React 19 First Look

react Mar 05, 2024

React 19 is just around the corner and it will include some major changes and improvements. In this post, we will look at some of those features but we will also jump into the experimental build and use some of them such as the use() hook, actions and some of the new hooks like useFormState, useFormStatus and useOptimistic.

The video version of this should be out soon at youtube.com/traversymedia.

You can get the final code for this post in this GitHub repo.

React Compiler

So you may notice that all of the big frameworks seem to copy each other, and React is no different. React 19 is taking a page from the Svelte and some of the other framework's playbook and they've created a new compiler that will compile your React code into regular JavaScript potentially doubling performance. And what's even better than any of the performance gains, at least in my opinion, is it's going to make React easier to work with.

I don't know about you but I feel like a lot of the newer frameworks like Svelte, Vue and even projects like Astro, just do a lot of things in a much easier and more intuitive way than React and even though it's the most popular front-end framework, it's kind of falling behind in that regard. So it looks like this compiler is going to bring React up to speed.

The Compiler will model both the rules of JavaScript and the “rules of React”. Things like immutable props and state values. So this will make React a bit more forgiving and will let you bend the rules a bit. However many developers want their code to abide by those rules, so in that case, you can enable React's strict mode and configure the React ESLint plugin

The compiler is being used now in production with Instagram. So it's not just some experimental thing that may or may not happen.

So many of the features that I'm going to talk about are in direct relation and possible because of this compiler. So let's talk about some of the changes that the compiler will bring in addition to just overall performance.

Automatic Memoization

So first off, it will do automatic memoization, which is the process of optimizing components to prevent unnecessary re-renders, which has been a pain point with React forever. So I'm talking about re-rendering components even when no props have changed or anything like that. Traditionally, we had to use hooks like useMemo and useCallback, which I always found to be annoying not just to use but also to teach. So I'm really excited to see this become automatic and truthfully, with most developers, including myself, it will use memoization in a more efficient way than we would do manually. So basically, we'll have better, cleaner, and more efficient code and we'll never need to use useMemo or useCallback again.

ref as a Regular Prop

This is a smaller but really nice change. The compiler will also make ref become a regular prop that it can be passed to any component. Traditionally, we use forwardRef lets your component expose a DOM node to the parent component with a ref.

It would look like this:

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  // ...
});

In React 19, we no longer need to do this. It can just be passed as a regular prop. This is going to make working with refs much easier and more intuitive.

use() Hook

One of the coolest features in terms of how we write our code is the new use() hook. This hook lets you read and asynchronously load the value of a resource like a promise or even context. In fact, use(context) is going to replace the useContext() hook and will make working with context and global state much easier. Which is a huge plus because again, I feel like the other frameworks have passed React when it comes to state.

Unlike other React Hooks, use() can be called within loops and conditional statements. I'm going to show you some examples of fetching data using the use() hook. You can also read more about it here

throw promise:

The React compiler has also simplified error handling when working with promises. Instead of throwing promises directly, you can use the use(promise) hook to handle asynchronous operations and errors.

<Context> Replaces <Context.Provider>

Another change to context is we'll no longer need to use <Context.Provider> to provide a value to a context. It wil now simply be <Context>.

React.lazy

When it comes to lazy loading components, the React Suspense Compiler (RSC) can be used instead.

So overall, these are the things that you really won't need to use in the future:

  • useMemo
  • useCallback
  • forwardRef
  • useContext
  • React.lazy

You can also minimize the amount of useEffect hooks you use because of the new use hook and server components.

Document Metadata

Another thing we won't really need anymore are 3rd party packages for SEO and metadata. React 19 will have built in support for metadata such as title, description, etc. This is something that we've always had to use a third party library for unless you used something like Next.js. This is going to make it much easier to manage metadata for SEO and social sharing. You can also put it anywhere in your component. It would work the same way in all environments, including fully client-side code and server-rendered code.

Actions API

Another huge change is the new Actions API. Up to this point with client-side React, we have to add a submit handler and then make our request for whatever it is that we're doing. Well now we can actually use the action attribute on a form to handle the submission. This is something that you may be familiar with if you've used Next.js or Remix but now it will be a stable feature of React and it can be used for both sever and client-only applications. It can also operate synchronously or asynchronously. This is going to make working with forms much easier and more intuitive. We also have new hooks such as useFormState and useFormStatus to help with form handling. We will look at some examples of how to use actions and the new form hooks in a little bit.

Actions are also automatically submitted within a transition which will keep the current page interactive while the action is processing. We also have the ability to use async/await in transitions. This will allow you to show a pending UI with the isPending state of a transition.

useOptimisitc Hook

Another hook that is nop longer experimental in React 19 is the useOptimistic hook. This hook can be used to apply temporary optimistic updates to the UI while waiting for things like a response from the server. Using this hook with actions, you can optimistically set the state of the data on the client such as showing a post that you submitted or showing a like, before you even get the response from the server. This just makes for a faster and more responsive experience. We're going to look at an example of using the useOptimistic hook along with a form action.

use client and use server Directives

So if you've used Next.js, then you've probably used React Server Components and the use client and use server directives. Now, React 19 will have built-in support for this. So this means better SEO, faster load times and easier data fetching. You can use these directives at the top of the component file. They allow developers to write both client-side and server-side code in the same file. Now obviously, you have to have a server to use this, but it's a huge step forward for React and I'm sure we'll see more meta frameworks emerge that will make it easier to use server components.

Asset Loading

Another improvement that has been made is asset loading. You've probably had times where when you load a page, you get that flicker of unstyled content. That shouldn't be an issue anymore because now, asset loading integrates with Suspense with resource loading, which means you'll have a much smoother experience when loading your app. So if you have a really high res image, asset loading will make sure that the image is ready before it's displayed.

Web Components

We also have better support for web components. React 19 will have better support for web components. i couldn't find much information on this, but it is listed as a feature. This will allow you to build better reusable components and even mix and match with other frameworks.

Examples

Let's look at some examples of some of the new features. First, we'll look at the new use hook and then we'll look at the new Actions API. I will have this code available in a repo at the end of the post if you need it. The project uses React Router and things are named a bit differently than they are in this blog post.

I simply set up a Vite project and ran:

npm install react@experimental react-dom@experimental

use() Hook

The use() hook can make things easier. Let's fetch a joke from the Chuck Norris API and display it in a component.

First we will do it with the standard useEffect and useState hooks:

import { useEffect, useState } from 'react';

const JokeItem = ({ joke }) => {
  return (
    <div className='bg-blue-50 shadow-md p-4 my-6 rounded-lg'>
      <h2 className='text-xl font-bold'>{joke.value}</h2>
    </div>
  );
};

const Joke = () => {
  const [joke, setJoke] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchJoke = async () => {
      try {
        const res = await fetch('https://api.chucknorris.io/jokes/random');
        const data = await res.json();
        setJoke(data);
        setLoading(false);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    };

    fetchJoke();
  }, []);

  if (loading) {
    return <h2 className='text-2xl text-center font-bold mt-5'>Loading...</h2>;
  }

  return (
    <div className='container mx-auto max-w-4xl'>
      <JokeItem joke={joke} />
    </div>
  );
};
export default Joke;

It's ok, but it's a bit long. Now let's do it with the new use() hook:

import { use, Suspense } from 'react';

const fetchData = async (id) => {
  const res = await fetch('https://api.chucknorris.io/jokes/random');
  return res.json();
};

const JokeItem = () => {
  const joke = use(fetchData());
  return (
    <div className='bg-blue-50 shadow-md p-4 my-6 rounded-lg'>
      <h2 className='text-xl font-bold'>{joke.value}</h2>
    </div>
  );
};

const Joke = () => {
  return (
    <Suspense
      fallback={
        <h2 className='text-2xl text-center font-bold mt-5'>Loading...</h2>
      }
    >
      <div className='container mx-auto max-w-4xl'>
        <JokeItem />
      </div>
    </Suspense>
  );
};
export default Joke;

We simply make the request in a function and return the promise. We don't use await before the res.json() because the use() hook will handle that for us. We then use the use() hook in the JokeItem component and we can access the value with joke.value. We then wrap the JokeItem component in a Suspense component and pass a fallback prop which is what will be displayed while the joke is being fetched. It is IMPORTANT that you wrap the component that uses the use hook in a Suspense component or you will get a never ending loop of requests.

Document Metadata Example

Let's throw some metadata into the Joke component:

const Joke = () => {
  return (
    <Suspense
      fallback={
        <h2 className='text-2xl text-center font-bold mt-5'>Loading...</h2>
      }
    >
      <title>Chuck Norris Jokes</title>
      <meta name='description' content='Chuck Norris jokes' />
      <meta name='keywords' content='chuck norris, jokes' />
      <div className='container mx-auto max-w-4xl'>
        <JokeItem />
      </div>
    </Suspense>
  );
};

export default Joke;

If you look at the title in the browser and the devtools, you will see the changes. This is still client-rendering so by default it will not be on the page source, but if you were working with server-side rendering or server components, it would be.

Actions API Examples

Here is an example of how we can use an action with a form to submit posts:

import { useState } from 'react';

// PostItem component
const PostItem = ({ post }) => {
  return (
    <div className='bg-blue-50 shadow-md p-4 my-6 rounded-lg'>
      <h2 className='text-xl font-bold'>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
};

// PostForm component
const PostForm = ({ addPost }) => {
  const formAction = async (formData) => {
    // We have direct access to the form data
    const newPost = {
      title: formData.get('title'),
      body: formData.get('body'),
    };

    addPost(newPost);
  };

  return (
    <form
      action={formAction}
      className='bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4'
    >
      <div className='mb-4'>
        <label
          className='block text-gray-700 text-sm font-bold mb-2'
          htmlFor='title'
        >
          Title
        </label>
        <input
          className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'
          id='title'
          type='text'
          placeholder='Enter title'
          name='title'
        />
      </div>
      <div className='mb-6'>
        <label
          className='block text-gray-700 text-sm font-bold mb-2'
          htmlFor='body'
        >
          Body
        </label>
        <textarea
          className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'
          id='body'
          rows='5'
          placeholder='Enter body'
          name='body'
        ></textarea>
      </div>
      <div className='flex items-center justify-between'>
        <button
          className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline'
          type='submit'
        >
          Submit
        </button>
      </div>
    </form>
  );
};

const Posts = () => {
  const [posts, setPosts] = useState([]);

  const addPost = (newPost) => {
    setPosts((posts) => [...posts, newPost]);
  };

  return (
    <div className='max-w-4xl mx-auto'>
      <PostForm addPost={addPost} />
      {posts.map((post, index) => (
        <PostItem key={index} post={post} />
      ))}
    </div>
  );
};
export default Posts;

As you can see we simply add an action and pass a function that will handle the form submission. We then have direct access to the form data and can do whatever we want with it. In this case we're adding a new post to the posts state.

useFormStatus Hook

We can use the useFormStatus hook to manage the status of the form. For example, if the form is submitting/pending, if there's an error, etc.

Let's use this hook in the PostForm component above to disable the button and change the text while the form is submitting.

Import the hook:

import { useFormStatus } from 'react-dom';

One of the pitfalls with this is that this hook only returns status information for a parent

and not for any
rendered in the same component calling the Hook, or child components.

So what we can do is create a separate component for the button itself. In the PostForm component cut the button and put it into a component called SubmitButton. Then use the useFormStatus hook in that component. We will log it as well to see what it returns.

We will also use the pending property to disable the button and change the text while the form is submitting.

Add this above the form component:

// SubmitButton component
const SubmitButton = () => {
  const { pending } = useFormStatus();
  console.log(pending);

  return (
    <button
      className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline'
      type='submit'
      disabled={pending}
    >
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
};

Now in the PostForm component we can use the SubmitButton component:

<div className='flex items-center justify-between'>
  <SubmitButton />
</div>

Now when you submit, you should see true logged to the console and the button should be disabled and the text should change to "Submitting...".

useFormState Hook`

We also have new hooks such as useFormState to help with form handling. useFormState is used to manage the state of the form and useFormStatus is used to manage the status of the form. For example, if the form is submitting, if there's an error, etc.

Let's take a look at an example of how we can use useFormState to manage the message state of an add to cart form:

import { useFormState } from 'react-dom';

const AddToCartForm = ({ itemID, itemTitle }) => {
  const [message, formAction] = useFormState(addToCart, null);

  return (
    <div className='max-w-4xl mx-auto'>
      <form
        action={formAction}
        className='bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4'
      >
        <h2 className='text-xl font-bold mb-4'>{itemTitle}</h2>
        <input type='hidden' name='itemID' value={itemID} />
        <button
          type='submit'
          className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline'
        >
          Add to Cart
        </button>
        <div className='mt-4 text-sm text-gray-700'>{message}</div>
      </form>
    </div>
  );
};

const addToCart = (prevState, queryData) => {
  const itemID = queryData.get('itemID');
  if (itemID === '1') {
    return 'Added to cart';
  } else {
    return "Couldn't add to cart: the item is sold out.";
  }
};

export default AddToCartForm;

We use useFormState and pass the addToCart function which will handle the form submission. We also pass an initial value of null for the message state. We then use the message state in the component to display a message to the user.

The function takes in the previous state and the form data.

Alright, so those are actions and the new form hooks.

useOptimisitc Hook

Let's take a look at an example of how we can use the useOptimistic hook to apply temporary optimistic updates to the UI while waiting for a response from the server.

import { useOptimistic, useState, useRef } from 'react';

function Thread({ messages, sendMessage }) {
  // Create a reference to the form
  const formRef = useRef();

  // This function is called when the form is submitted
  async function formAction(formData) {
    addOptimisticMessage(formData.get('message'));

    // Clear the form
    formRef.current.reset();

    await sendMessage(formData);
  }

  // The useOptimistic hook is used to add an optimistic message to the list of messages
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );

  return (
    <div className='container mx-auto max-w-4xl'>
      <form
        action={formAction}
        ref={formRef}
        className='flex items-center mb-5'
      >
        <input
          type='text'
          name='message'
          placeholder='Hello!'
          className='border border-gray-300 rounded py-1 px-2 mr-2 focus:outline-none focus:border-blue-500'
        />
        <button
          type='submit'
          className='bg-blue-500 hover:bg-blue-600 text-white font-semibold py-1 px-4 rounded focus:outline-none focus:shadow-outline'
        >
          Send
        </button>
      </form>
      {optimisticMessages.map((message, index) => (
        <div key={index} className='flex items-center'>
          <span>{message.text}</span>
          {message.sending && (
            <small className='ml-1 text-gray-500'>(Sending...)</small>
          )}
        </div>
      ))}
    </div>
  );
}

const deliverMessage = async (message) => {
  // Simulate a delay
  await new Promise((res) => setTimeout(res, 1000));
  return message;
};

const Message = () => {
  const [messages, setMessages] = useState([]);

  async function sendMessage(formData) {
    const sentMessage = await deliverMessage(formData.get('message'));

    setMessages((messages) => [...messages, { text: sentMessage }]);
  }

  return <Thread messages={messages} sendMessage={sendMessage} />;
};

export default Message;

Here is the explanation of the code above:

  • The component implements a chat thread interface allowing users to send messages.

  • It uses the useRef hook to create a reference to the form element.

  • The formAction function handles form submission, adding an optimistic message, clearing the form, and calling the sendMessage function.

  • The useOptimistic hook manages optimistic updates for the list of messages, adding new messages with a "sending" status.

  • The optimisticMessages array is mapped to display messages and their sending status.

  • The deliverMessage function simulates sending a message with a 1-second delay.

  • The Message component manages the state of the message list using the useState hook and provides the sendMessage function to the Thread component.

So those are some examples of some of the new features in React 19. You can get all of the code in this GitHub repo along with some other examples.

Stay connected with news and updates!

Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.

We hate SPAM. We will never sell your information, for any reason.