React 19 setup and use of server actions
Tags: react
Published: 2024-07-05 04:22:45

TLDR: [Github repo](https://github.com/roggc/react-19)

## `npm init -y`

Create a folder and run `npm init -y` in it.

## Install dependencies

Install the dependencies with the following command:

```bash
npm i webpack webpack-cli react@rc react-dom@rc react-server-dom-webpack@rc babel-loader @babel/core @babel/register @babel/preset-react @babel/plugin-transform-modules-commonjs express
```

## Configure Babel

Add this to your `package.json`:

```json
  "babel": {
    "presets": [
      [
        "@babel/preset-react",
        {
          "runtime": "automatic"
        }
      ]
    ]
  }
```

## Add scripts to `package.json`

Add the following scripts to your `package.json`:

```json
"dev": "webpack -w",
"start": "node --conditions react-server server/server.js"
```

## This is how your `package.json` must look

```json
{
  "name": "react-19-essay",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack -w",
    "start": "node --conditions react-server server/server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "^7.24.7",
    "@babel/plugin-transform-modules-commonjs": "^7.24.7",
    "@babel/preset-react": "^7.24.7",
    "@babel/register": "^7.24.6",
    "babel-loader": "^9.1.3",
    "express": "^4.19.2",
    "react": "^19.0.0-rc-f38c22b244-20240704",
    "react-dom": "^19.0.0-rc-f38c22b244-20240704",
    "react-server-dom-webpack": "^19.0.0-rc-f38c22b244-20240704",
    "webpack": "^5.92.1",
    "webpack-cli": "^5.1.4"
  },
  "babel": {
    "presets": [
      [
        "@babel/preset-react",
        {
          "runtime": "automatic"
        }
      ]
    ]
  }
}
```

## Create the `webpack.config.js` file

At the root of the project, create a `webpack.config.js` file with the following content:

```javascript
const path = require("path");
const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");

module.exports = {
  mode: "development",
  entry: [path.resolve(__dirname, "./src/index.js")],
  output: {
    path: path.resolve(__dirname, "./public"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: "babel-loader",
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [new ReactServerWebpackPlugin({ isServer: false })],
};
```

From the look to this configuration file, we know we need a `src/index.js` file.

## Create a `src/index.js` file

Create a `src` folder and put in it the following file (`index.js`):

```javascript
import { use } from "react";
import { createFromFetch } from "react-server-dom-webpack/client";
import { createRoot } from "react-dom/client";

const root = createRoot(document.getElementById("root"));
root.render(<Root />);

const cache = new Map();

function Root() {
  let content = cache.get("home");
  if (!content) {
    content = createFromFetch(fetch("/react"));
    cache.set("home", content);
  }

  return <>{use(content)}</>;
}
```

You see how we use `createFromFetch` from `react-server-dom-webpack/client`.

You also see how we fetch to `/react` endpoint.

## Create a `server/server.js` file

Create a `server` folder and put in it the following file (`server.js`):

```javascript
const register = require("react-server-dom-webpack/node-register");
register();
const path = require("path");
const { readFileSync } = require("fs");
const babelRegister = require("@babel/register");

babelRegister({
  ignore: [/[\\\/](build|server|node_modules)[\\\/]/],
  presets: [["@babel/preset-react", { runtime: "automatic" }]],
  plugins: ["@babel/transform-modules-commonjs"],
});

const { renderToPipeableStream } = require("react-server-dom-webpack/server");

const express = require("express");

const React = require("react");
const ReactApp = require("../src/app").default;

const app = express();

app.get("/", (req, res) => {
  const html = readFileSync(
    path.resolve(__dirname, "../public/index.html"),
    "utf8"
  );
  res.send(html);
});

app.get("/react", (req, res) => {
  const manifest = readFileSync(
    path.resolve(__dirname, "../public/react-client-manifest.json"),
    "utf8"
  );
  const moduleMap = JSON.parse(manifest);
  const { pipe } = renderToPipeableStream(
    React.createElement(ReactApp),
    moduleMap
  );
  pipe(res);
});

app.use(express.static(path.resolve(__dirname, "../public")));
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`listening on port ${port}`));
```

You see how we use `renderToPipeableStream` from `react-server-dom-webpack/server`. You also see how we require a `src/app.js` file. You also see how we use the `public` folder for static files.

## Create a `public/index.html` file

Create a `public` file and put in it the following file (`index.html`):

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="description" content="React with Server Components" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>fun!</title>
    <script defer src="main.js"></script>
  </head>

  <body>
    <div id="root"></div>
  </body>
</html>
```

## The `src` folder

Until here the setup. Now we will focus on creating code for our app. First we need to create the `app.js` file, the entry point of our app (a React component).

```javascript
"use client";

import Comp1 from "./non-bloking-server-actions-components/comp1/callable";

export default function () {
  return <Comp1 name="Albert" />;
}
```
 
`Comp1` is in this case a client component that calls a none bloking server action:

```javascript
"use client";

import action from "./action";

export default function (props) {
  return action(props);
}
```

`action` is the none bloking server action:

```javascript
"use server";

import Output from "./output";

export default function ({ name }) {
  const messagePromise = new Promise((res) =>
    setTimeout(() => res("hello x " + name), 2000)
  );

  return <Output greetingPromise={messagePromise} />;
}
```

`Output` is a React Client Component:

```javascript
"use client";

import Counter from "../../pure-client-components/counter";
import Wrapper from "../../wrapper";

export default function ({ greetingPromise }) {
  return (
    <>
      <div>
        <Wrapper>{greetingPromise}</Wrapper>
      </div>
      <Counter />
    </>
  );
}
```

`Wrapper` is this React Client Component:

```javascript
"use client";

import { Suspense } from "react";
import ErrorBoundary from "./error-boundary";

export default function Wrapper({ children }) {
  return (
    <ErrorBoundary fallback={<div>Something crashed.</div>}>
      <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
    </ErrorBoundary>
  );
}
```

and `Counter` is a regular React Client Component:

```javascript
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return <button onClick={increment}>{count}</button>;
}
```

## Conclusion

You see how with the use of non blocking server actions that return client components, server components are not necessary. What we see in this example is counter available and interactive from the first moment, and a loading indicator for a greeting message.

## References

The original setup for a working implementation of React Server Components without a framework was taken from [here](https://www.reddit.com/r/react/comments/16jq5z4/tutorial_how_to_use_react_server_components_rsc/).