Search code examples
reactjsreact-server-components

How to correctly mark component as server with React 19 beta


I have the following basic code in two separate files designed to test server components in the React 19 beta:

// file: App.jsx
'use client';
import React, { Suspense, useState } from 'react';
import TestServer from './TestServer';
import clsx from 'clsx';

export default function App() {
    const [red, setRed] = useState(false);

    return (
        <div className={clsx("p-3 m-3", red && "border border-2 border-danger")}>
            <Suspense fallback={<p>Loading...</p>}>
                <header>
                    <h1>React 19 Test</h1>
                    <TestServer timeout={2000} />
                    <button onClick={() => setRed(!red)}>Toggle Red Border</button>
                </header>
            </Suspense>
        </div>
    );
}

// file: TestServer.jsx
'use server';

export default async function TestServer({timeout = 1000}) {
    const response = await new Promise((resolve) => {
        setTimeout(() => {
          resolve('Server is running');
        }, timeout);
    });

    return <p>{response}</p>;
}

This seems to work as expected when I run it. The component suspends for 2 seconds, showing the loading text, then displays the response and button until you press the button, at which point it restarts the process with the updated "red" value, which is demonstrated by an outline.

However, looking into the console I get the warnings

async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

and

A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.

The way I'm reading this, I think my server component is being run on the client still, thus the fact that I manually create a promise and set the TestServer component to be async triggers the warnings. What is the correct way to implement this test such that these warnings do not appear?


Solution

  • In React 19 Server Components are the default component type, you don't need to specify the use server directive. From the Server Components documentation there is a Note:

    A common misunderstanding is that Server Components are denoted by "use server", but there is no directive for Server Components. The "use server" directive is used for Server Actions.

    Server Actions are generally used by <form>s or within useTransition, so you appear to be interested in actual Server Components combined with Client Components.

    Here is an example on how you can refactor your code. It basically reverses the client/server logic from each file (which could be named more appropriately but that's another issue), and streams the server response to the client via Suspense on the server and use on the client to handle the Promise.

    TestServer.tsx (This is really your client component)

    'use client';
    
    import { use, useState } from 'react';
    
    type Props = {
      response: Promise<string>;
    };
    
    export default function TestServer({ response }: Props) {
      const [red, setRed] = useState(false);
      const serverData = use(response);
    
      return (
        <div style={{ border: `1px solid ${red ? 'red' : 'black'}` }}>
          <header>
            <h1>React 19 Test</h1>
            <p>{serverData}</p>
            <button onClick={() => setRed(!red)}>Toggle Red Border</button>
          </header>
        </div>
      );
    }
    

    App.tsx (your server component)

    import { Suspense } from 'react';
    
    import TestServer from './TestServer';
    
    async function getResponse(timeout = 1000) {
      const response = await new Promise((resolve) => {
        setTimeout(() => {
          resolve('Server is running');
        }, timeout);
      });
    
      return response;
    }
    export default function App({ timeout = 1000 }) {
      const response = getResponse(timeout);
    
      return (
        <Suspense fallback={<p>Loading ...</p>}>
          <TestServer response={response} />
        </Suspense>
      );
    }
    

    I removed use of clsx as it wasn't relevant to the question and I didn't know want to tie in Tailwind or whatever you are using to manage CSS.

    There is a working example on StackBlitz.