RE

React 19 Features

React · 8 entries

use() Hook

syntax
const value = use(resource);
// resource can be a Promise or a Context
example
import { use, Suspense } from 'react';

function UserGreeting({ userPromise }) {
  // React suspends until the promise resolves
  const user = use(userPromise);
  return <h2>Hello, {user.name}!</h2>;
}

function App() {
  const userPromise = fetchCurrentUser();

  return (
    <Suspense fallback={<p>Loading user...</p>}>
      <UserGreeting userPromise={userPromise} />
    </Suspense>
  );
}

Note Unlike other hooks, use() can be called inside if statements and loops. When reading a promise, wrap the component in Suspense. The promise should be created outside the component or cached -- do not create new promises inside render.

use() with Context

syntax
const value = use(SomeContext);
// Can be called conditionally, unlike useContext
example
import { use, createContext } from 'react';

const FeatureFlagContext = createContext({ darkMode: false });

function Banner({ showThemed }) {
  // Reading context conditionally -- not possible with useContext
  if (showThemed) {
    const { darkMode } = use(FeatureFlagContext);
    return (
      <div style={{ background: darkMode ? '#222' : '#eee' }}>
        Themed banner
      </div>
    );
  }
  return <div>Standard banner</div>;
}

Note use(Context) works like useContext but can be placed after early returns or inside conditionals. This is useful when you only need the context value in some code paths.

Actions (Form Actions)

syntax
<form action={actionFn}>
  ...
</form>
// actionFn receives FormData automatically
example
function ContactForm() {
  async function submitContact(formData) {
    const name = formData.get('name');
    const email = formData.get('email');
    await sendContactRequest({ name, email });
  }

  return (
    <form action={submitContact}>
      <input name="name" placeholder="Your name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit">Send</button>
    </form>
  );
}

Note Actions are async functions passed to the action prop of <form>. They receive FormData as an argument. React handles the pending state and error transitions automatically. Actions work with useActionState and useFormStatus for richer patterns.

useFormStatus

syntax
const { pending, data, method, action } = useFormStatus();
example
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Saving...' : 'Save Changes'}
    </button>
  );
}

function EditProfile() {
  async function updateProfile(formData) {
    await saveProfile(Object.fromEntries(formData));
  }

  return (
    <form action={updateProfile}>
      <input name="displayName" placeholder="Display name" />
      <input name="location" placeholder="Location" />
      <SubmitButton />
    </form>
  );
}

Note useFormStatus must be called from a component rendered inside a <form>. It will not work if called in the same component that renders the form -- it reads from the nearest parent form. Import it from 'react-dom', not 'react'.

useOptimistic

syntax
const [optimisticState, setOptimistic] = useOptimistic(
  actualState,
  updateFn? // (currentState, optimisticValue) => newState
);
example
import { useOptimistic, useState } from 'react';

function MessageThread({ initialMessages }) {
  const [messages, setMessages] = useState(initialMessages);
  const [optimisticMessages, addOptimistic] = useOptimistic(
    messages,
    (current, newMsg) => [...current, { ...newMsg, sending: true }]
  );

  async function sendMessage(formData) {
    const text = formData.get('message');
    const tempMsg = { id: Date.now(), text, sending: true };
    addOptimistic(tempMsg);

    const saved = await postMessage(text);
    setMessages(prev => [...prev, saved]);
  }

  return (
    <div>
      {optimisticMessages.map(msg => (
        <p key={msg.id} style={{ opacity: msg.sending ? 0.6 : 1 }}>
          {msg.text}
        </p>
      ))}
      <form action={sendMessage}>
        <input name="message" />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

Note useOptimistic shows a temporary state while an async action is in progress. When the action completes (or fails), React reverts to the actual state. The optimistic value is automatically rolled back if the action throws.

useActionState

syntax
const [state, formAction, isPending] = useActionState(
  actionFn,
  initialState
);
// actionFn: (previousState, formData) => newState
example
import { useActionState } from 'react';

function LoginForm() {
  const [state, loginAction, isPending] = useActionState(
    async (prevState, formData) => {
      const email = formData.get('email');
      const password = formData.get('password');

      try {
        await authenticateUser(email, password);
        return { error: null, success: true };
      } catch (err) {
        return { error: err.message, success: false };
      }
    },
    { error: null, success: false }
  );

  return (
    <form action={loginAction}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      {state.error && <p className="error">{state.error}</p>}
      <button disabled={isPending}>
        {isPending ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  );
}

Note useActionState combines action handling with state management. The action function receives the previous state as its first argument and FormData as its second. The third return value (isPending) tells you if the action is running.

ref as a Prop

syntax
// React 19: no forwardRef needed
function MyComponent({ ref, ...props }) {
  return <div ref={ref} {...props} />;
}
example
function TextArea({ ref, label, ...rest }) {
  return (
    <div className="field">
      <label>{label}</label>
      <textarea ref={ref} {...rest} />
    </div>
  );
}

function CommentForm() {
  const textareaRef = useRef(null);

  function handleReset() {
    textareaRef.current.value = '';
    textareaRef.current.focus();
  }

  return (
    <div>
      <TextArea ref={textareaRef} label="Comment" rows={4} />
      <button onClick={handleReset}>Clear</button>
    </div>
  );
}

Note React 19 passes ref as a regular prop to function components. forwardRef is deprecated -- migrate by moving ref into the props destructuring. Existing forwardRef code continues to work but will show deprecation warnings.

Document Metadata

syntax
// Render <title>, <meta>, <link> directly in components
function Page() {
  return (
    <>
      <title>Page Title</title>
      <meta name="description" content="..." />
      {/* page content */}
    </>
  );
}
example
function ProductPage({ product }) {
  return (
    <article>
      <title>{product.name} | ShopApp</title>
      <meta name="description" content={product.summary} />
      <link rel="canonical" href={`/products/${product.slug}`} />

      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <strong>${product.price}</strong>
    </article>
  );
}

// React hoists these tags into the <head> automatically

Note In React 19, metadata tags rendered inside components are automatically hoisted into the document <head>. This eliminates the need for third-party helmet libraries for basic metadata management. Works with SSR and client rendering.