# Using client components

In this tutorial, you will learn how to use client components to create interactive elements in your application. Enhance your user experience with client-side rendering and dynamic content updates.

We will build a simple photo gallery application that will use client components for navigation and image display. You will learn how to create client components and handle user interactions.

We will also use the file-system based routing solution provided by `@lazarv/react-server` to create routes for our application. This will allow us to navigate between different pages and display the appropriate content.

## Project setup

To get started, create a new React application using the following command:

```sh
mkdir photos
cd photos
pnpm init
pnpm add @lazarv/react-server react-click-away-listener @faker-js/faker
pnpm add -D @types/react @types/react-dom autoprefixer postcss tailwindcss@3 typescript typescript-plugin-css-modules
pnpx tailwindcss@3 init -p
mkdir src
mkdir src/app
mkdir src/components
```

We need to modify `tailwind.config.js` to include the following configuration:

```js filename="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/components/**/*.{js,ts,jsx,tsx}",
    "./src/app/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
};
```

With these changes we enabled Tailwind CSS to scan our components and app directories for styles to include in the final build and added support for radial and conic gradients.

Create the `global.css` file needed for importing Tailwind CSS styles in the `src/app` directory:

```css filename="src/app/global.css"
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Our `tsconfig.json` will look like this:

```json filename="tsconfig.json"
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "jsx": "preserve",
    "strict": true,
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "types": ["react/experimental", "react-dom/experimental"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "plugins": [{ "name": "typescript-plugin-css-modules" }]
  },
  "include": ["**/*.ts", "**/*.tsx", ".react-server/**/*.ts"],
  "exclude": ["**/*.js", "**/*.mjs"]
}
```

We enabled the `typescript-plugin-css-modules` plugin to support CSS modules in our project and we also include `.react-server/**/*.ts` files in our project to enable typed routing when using the file-system based router with the built-in `Link` component.

When using the file-system based router we can specify the root directory for our routes in the `react-server.config.json` file so that the router knows where to look for the routes without crawling the entire project directory tree. This is especially useful for larger projects with many directories and files. Create the `react-server.config.json` file in the root of your project with the following content:

```json filename="react-server.config.json"
{
  "root": "src/app"
}
```

## Generating random photos

Let's start by creating a new file called `src/photos.ts` with the following content:

```ts filename="src/photos.ts"
import { faker } from "@faker-js/faker";

export type Photo = {
  id: string;
  username: string;
  imageSrc: string;
};

const photos: Photo[] = new Array(9).fill(null).map((_, index) => ({
  id: `${index}`,
  username: faker.internet.userName(),
  imageSrc: faker.image.urlPicsumPhotos(),
}));

export default photos;
```

This file will generate an array of 9 random photos using the `@faker-js/faker` package. Each photo will have an `id`, `username`, and `imageSrc` property. This will be our random photo set for the gallery, our data source for the application.

## Routes

In this simple photos app we will have only one main route that will display the gallery and a router outlet for rendering a modal view of a single photo.

### Layout

We want to wrap all of our pages into an HTML document layout, so we need to create the `src/app/(root).layout.tsx` file with the following content:

```tsx filename="src/app/(root).layout.tsx"
import "./global.css";

export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <title>Photos</title>
        <meta
          name="description"
          content="A sample app showing dynamic routing with modals as a route."
        />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        {children}
      </body>
    </html>
  );
}
```

This layout will be used for all routes in our application. It includes the global CSS styles and sets the title and description of the page. This is a React Server Component that will be rendered only on the server-side.

We named the layout file `(root).layout.tsx` because it will be used as the root layout for all routes in our application. `(root)` is only a name for us to identify the file better as the router will omit all parts of the file name which are in parentheses. The instrument the router to use our file as a layout by using the `.layout.tsx` suffix. The router will apply this layout to all subsequent routes.

### Gallery

To create our main gallery view and our index page, we need to create the `src/app/page.tsx` file with the following content:

```tsx filename="src/app/page.tsx"
import { Link } from "@lazarv/react-server/navigation";

import swagPhotos from "../photos";

export const ttl = 30000;

export default function Home() {
  const photos = swagPhotos;

  return (
    <main className="container mx-auto">
      <h1 className="text-center text-4xl font-bold m-10">Photos</h1>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 auto-rows-max	 gap-6 m-10">
        {photos.map(({ id, imageSrc }) => (
          <Link
            key={id}
            to={`/photos/${id}`}
            prefetch
            ttl={30000}
            rollback={30000}
          >
            <img
              alt=""
              src={imageSrc}
              height={500}
              width={500}
              className="w-full object-cover aspect-square"
            />
          </Link>
        ))}
      </div>
    </main>
  );
}
```

This page will display the gallery of photos. We use the `@lazarv/react-server/navigation` module to create a link to each photo in the gallery. The `prefetch`, `ttl`, and `rollback` props are used to optimize the navigation experience by preloading the photo data and caching it for a certain amount of time.

You can notice, that the `to` prop of the `Link` component is set to `/photos/${id}`. This is the path that the router will use to match the route and render the appropriate component for dispaying the photo. This `to` prop is also type-safe and will be checked against the routes defined in the `src/app` directory.

The default exported function `Home` is a React Server Component that will be rendered on the server-side. But the `Link` component is a client component that will be rendered on the server-side and then it will get hydrated on the client-side. This is how we can mix server and client components in our application with ease. The image element used as the child of the `Link` component is rendered only on the server-side. Only components annotated with the `"use client";` directive will be loaded on the client-side to be able to use any client-side features or state.

Using the `Link` component our new page will be loaded using client-side navigation and the browser will not reload the page when navigating between the photos. The payload of the page where the user will navigate will be not an entire HTML document, but only the React Server Component payload needed to render the page. This will make the navigation faster and more responsive.

## Modal

Let's create our first own client component that will display a modal view of a single photo. Create the `src/components/modal/Modal.tsx` file with the following content:

```tsx filename="src/components/modal/Modal.tsx"
"use client";

import { useEffect } from "react";
import ClickAwayListener from "react-click-away-listener";

export default function Modal({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    document.body.classList.add("overflow-hidden");
    return () => document.body.classList.remove("overflow-hidden");
  }, []);

  useEffect(() => {
    function handleKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        history.back();
      }
    }

    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, []);

  return (
    <div className="fixed z-10 left-0 right-0 top-0 bottom-0 mx-auto bg-black/60">
      <ClickAwayListener onClickAway={() => history.back()}>
        <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-3xl sm:w-10/12 md:w-8/12 lg:w-1/2 p-6">
          {children}
        </div>
      </ClickAwayListener>
    </div>
  );
}
```

The most important part is the first line of the file: `"use client";`. This directive tells the runtime that this component should be loaded on the client-side. This is how we can create client components. The rest of the file is a simple modal component that will display the photo in a modal view. We use the `useEffect` hook to add and remove the `overflow-hidden` class from the `body` element to prevent scrolling when the modal is open and another `useEffect` hook to handle keyboard navigation. We also use the `ClickAwayListener` component from the `react-click-away-listener` package to close the modal when the user clicks outside of it. You don't need to specify `"use client";` directive for the `ClickAwayListener` component, because it is imported by our `Modal` component which is already a client component.

In client components, you can use all of the React hooks only working on the client-side, like the `useEffect` hooks to handle browser events and you can also use React components which needs to run on the client-side, in the browser, like the `ClickAwayListener` component from the `react-click-away-listener` package.

The `children` prop could be any type of React component. It can be a React Server Component or another client component. In this case, we will use it to display the photo in the modal view.

All client components are rendered on the server-side and then they are hydrated on the client-side. This means that the server will render the component and send it to the client, where it will be re-rendered and the client-side logic will be applied.

## Photo

Now we need to create a route for displaying a single photo in a modal view. Create the `src/app/@modal/photos/[id].page.tsx` file with the following content:

```tsx filename="src/app/@modal/photos/[id].page.tsx"
import Frame from "../../../components/frame/Frame";
import Modal from "../../../components/modal/Modal";
import photos from "../../../photos";

export default function PhotoModal({ id: photoId }: { id: string }) {
  const photo = photos.find((p) => p.id === photoId);

  return (
    <Modal>{!photo ? <p>Photo not found!</p> : <Frame photo={photo} />}</Modal>
  );
}
```

This page will display a single photo in a modal view. We use the `@modal` pattern in the directory name to tell the router that this route should be rendered as an outlet named `modal`, so it can be used over the main gallery page. The `photos` array is imported from the `src/photos.ts` file and we use the `photoId` parameter to find the photo with the matching `id`.

The `Frame` component is a simple component that will display the photo in the modal view. Create the `src/components/frame/Frame.tsx` file with the following content:

```tsx filename="src/components/frame/Frame.tsx"
import { Photo } from "../../photos";

export default function Frame({ photo }: { photo: Photo }) {
  return (
    <>
      <img
        alt=""
        src={photo.imageSrc}
        height={600}
        width={600}
        className="w-full object-cover aspect-square col-span-2"
      />

      <div className="bg-white p-4 px-6">
        <p>Taken by {photo.username}</p>
      </div>
    </>
  );
}
```

This component is a React Server Component. It will display the photo and the username of the user who took the photo.

Now if we go back to our outlet route, we can see that we are using the `Frame` component to display the photo in the modal view using our `Modal` client component. We can mix React Server Components and client components in our application to create dynamic and interactive user interfaces and optimize the rendering process. We only need to use client components for interactive or dynamic parts of our application, like modals, popups, JavaScript-driven animations, or other client-side features.

Now we can return to our layout component and include our new outlet to render the modal when the route matches the `/photos/[id]` pattern. Update the `src/app/(root).layout.tsx` file with the following content:

```tsx filename="src/app/(root).layout.tsx"
import "./global.css";

export default function Layout({
  modal,
  children,
}: React.PropsWithChildren<{
  modal: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <title>Photos</title>
        <meta
          name="description"
          content="A sample app showing dynamic routing with modals as a route."
        />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        {children}
        {modal}
      </body>
    </html>
  );
}
```

Layouts using the file-system based router will receive all subsequent outlets as props. The `modal` prop will contain the component rendered by the modal outlet when the route matches. We can use this prop to render the modal view in our layout component over the gallery view.

## Optimizing the navigation

We can also optimize the navigation by using the `ReactServerComponent` component to enable client-side navigation for the modal view. We can use the outlet prop for initial content on a page refresh. Using `ReactServerComponent` together with the `Link` component will result in using a smaller network payload as the link will update the modal outlet just by rendering the outlet only on the server-side as RSC payload. The payload will be 0.1x only of the size of re-rendering the whole page as RSC, getting from ~15k to only ~1.5k in size. Imagine how much impact this can have on a much more complex application. Update the `src/app/(root).layout.tsx` file with the following content:

```tsx filename="src/app/(root).layout.tsx"
import { ReactServerComponent } from "@lazarv/react-server/navigation";

import "./global.css";

export default function Layout({
  modal,
  children,
}: React.PropsWithChildren<{
  modal: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <title>Photos</title>
        <meta
          name="description"
          content="A sample app showing dynamic routing with modals as a route."
        />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        {children}
        <ReactServerComponent outlet="modal">
          {modal}
        </ReactServerComponent>
      </body>
    </html>
  );
}
```

## Running the application

To run the application, add the following scripts to your `package.json` file:

```json filename="package.json"
{
  "scripts": {
    "dev": "react-server",
    "build": "react-server build",
    "start": "react-server start"
  }
}
```

Using the file-system based router, we no longer need to specify the entrypoint of our application. The router will automatically find the routes in the `src/app` directory and render the appropriate component for each route.

Now you can run the application using the following command:

```sh
pnpm dev
```

Open your browser and navigate to `http://localhost:3000` to see the photo gallery application. Click on any photo to open the modal view and see the photo in a larger size.

To build the application for production, run the following command:

```sh
pnpm build
```

You can start the production server using the following command:

```sh
pnpm start
```

## Conclusion

In this tutorial, you learned how to use client components to create interactive elements in your application. You built a simple photo gallery application that uses client components for navigation and image display. You also learned how to create routes for your application using the file-system based routing solution provided by `@lazarv/react-server`. You can also find this example application in the [GitHub repository](https://github.com/lazarv/react-server/tree/main/examples/photos) and after cloning the repo and installing the dependencies by executing `pnpm install` you can run the application using the `pnpm --filter ./examples/photos dev` command.