# Project scaffold · A simple Chromia app with Rell and React · Learn Chromia

> This lesson walks through the project scaffold. You will see how the frontend talks to the Rell dapp, with most of the attention on page layout and design.

Source: https://chromia.com/learn/my-news-feed/module-two/scaffold/

A simple Chromia app with Rell and ReactLesson 24 of 27

1. [01Building a simple Chromia app with Rell, React and FT4](https://chromia.com/learn/my-news-feed/introduction/)
2. [02Project setup complete](https://chromia.com/learn/my-news-feed/setup/)

Module 1: A Rell backend with FT accounts

1. [03Overview](https://chromia.com/learn/my-news-feed/module-one/)
2. [04Part 1: Designing the database schema](https://chromia.com/learn/my-news-feed/module-one/data-modeling/)
3. [05The data model](https://chromia.com/learn/my-news-feed/module-one/data-modeling/tables/)
4. [06Code the model in Rell](https://chromia.com/learn/my-news-feed/module-one/data-modeling/model/)
5. [07Part 2: Set up accounts](https://chromia.com/learn/my-news-feed/module-one/create-accounts/)
6. [08FT4 accounts configuration](https://chromia.com/learn/my-news-feed/module-one/create-accounts/install-configure-ft4/)
7. [09Authentication with FT4 accounts](https://chromia.com/learn/my-news-feed/module-one/create-accounts/authentication/)
8. [10Part 3: A closer look at operations and queries](https://chromia.com/learn/my-news-feed/module-one/operations-queries/)
9. [11Basic operations](https://chromia.com/learn/my-news-feed/module-one/operations-queries/basic-operations/)
10. [12Basic queries](https://chromia.com/learn/my-news-feed/module-one/operations-queries/write-queries/)
11. [13Part 4: Validating and verifying input](https://chromia.com/learn/my-news-feed/module-one/input-verification/)
12. [14Verify inputs](https://chromia.com/learn/my-news-feed/module-one/input-verification/input-verification/)
13. [15Run unit tests](https://chromia.com/learn/my-news-feed/module-one/input-verification/tests/)
14. [16Part 5: How the dapp project is organized](https://chromia.com/learn/my-news-feed/module-one/project-structure/)
15. [17Work with Rell modules](https://chromia.com/learn/my-news-feed/module-one/project-structure/modules/)
16. [18Add modules to your dapp](https://chromia.com/learn/my-news-feed/module-one/project-structure/incorporate-modules/)
17. [19Part 6: Sign up users with an EVM wallet](https://chromia.com/learn/my-news-feed/module-one/register-evm-accounts/)
18. [20Sign up accounts with EVM wallets](https://chromia.com/learn/my-news-feed/module-one/register-evm-accounts/register-evm-accounts/)
19. [21Test the registration](https://chromia.com/learn/my-news-feed/module-one/register-evm-accounts/test-registration/)

Module 2: The React project

1. [22Overview](https://chromia.com/learn/my-news-feed/module-two/)
2. [23Set up the project](https://chromia.com/learn/my-news-feed/module-two/setup/)
3. [24Project scaffold](https://chromia.com/learn/my-news-feed/module-two/scaffold/)
4. [25Connect the client](https://chromia.com/learn/my-news-feed/module-two/connecting-the-client/)
5. [26Summary and manual testing](https://chromia.com/learn/my-news-feed/module-two/summary-and-tests/)

1. [27What is next?](https://chromia.com/learn/my-news-feed/what-next/)

[Learn](https://chromia.com/learn/)[A simple Chromia app with Rell and React](https://chromia.com/learn/my-news-feed/)

24 / 27Module 2: The React projectBeginner

# Project scaffold

This lesson walks through the project scaffold. You will see how the frontend talks to the Rell dapp, with most of the attention on page layout and design.

## Overview

The dapp has three main pages:

1. **Home page**: shows the news feed.
2. **New post page**: where a user writes a new post for their feed.
3. **Users page**: lists every user in the system and lets you follow or unfollow them.

## Basic layout

Start with a base layout. In `src/app/layout.tsx`, wrap the `{children}` element in a main div. Every page renders inside it.

src/app/layout.tsx

```
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <main className="flex min-h-screen justify-center bg-gray-100 text-black">
          <div className="container">{children}</div>
        </main>
      </body>
    </html>
  );
}
```

The result is a clean, responsive design that looks the same on every page.

## Navigation

To move between pages, add a navigation component.

src/components/NavBar.tsx

```
import Link from "next/link";

export default function NavBar() {
  return (
    <nav className="py-2 px-6">
      <ul className="flex justify-between">
        <li>
          <Link href="/" className="text-xl font-extrabold">
            News feed dapp
          </Link>
        </li>
        <div className="flex gap-4 font-semibold">
          <li>
            <Link href="/new-post" className="hover:text-gray-600">
              New Post
            </Link>
          </li>
          <li>
            <Link href="/users" className="hover:text-gray-600">
              Users
            </Link>
          </li>
          <li>
            <Link href="/" className="hover:text-gray-600">
              Feed
            </Link>
          </li>
        </div>
      </ul>
    </nav>
  );
}
```

Run the app to confirm it works. When you click a link, the URL changes to match.

shell

```nolines
$ npm run dev
```

## News feed (home) page

The home page uses the component in `src/components/NewsFeed.tsx`:

src/components/NewsFeed.tsx

```
export default function NewsFeed() {
  return (
    <div className="p-4 md:p-8">
      <div className="flex justify-between items-center">
        <h1 className="text-2xl font-bold">User name</h1>

        <div className="flex text-center">
          {/* Followers Box */}
          <div className="bg-white m-1 p-2 rounded-lg shadow">
            <h3 className="text-lg font-semibold">Followers</h3>
            <p className="text-3xl font-bold">0</p>
          </div>

          {/* Following Box */}
          <div className="bg-white m-1 p-2 rounded-lg shadow">
            <h3 className="text-lg font-semibold">Following</h3>
            <p className="text-3xl font-bold">0</p>
          </div>
        </div>
      </div>

      {/* News Feed */}
      <div className="bg-white p-4 rounded-lg shadow">
        <ul>
          <li key={0} className="mb-4">
            <div className="flex">
              <div className="font-semibold">User1</div>
              <div className="text-gray-500 text-sm ml-2">
                {new Date().toLocaleString()}
              </div>
            </div>
            <div className="mt-2">Some content</div>
            {/* Add a horizontal line between posts */}
            {<hr className="my-4 border-t border-gray-300" />}
          </li>
        </ul>
      </div>
    </div>
  );
}
```

## New post page

On this page, users write a post for the news feed. The page has a free text field and a button that sends the request.

src/components/NewPost.tsx

```
import { useRouter } from "next/navigation";
import { useState } from "react";

export default function NewPost() {
  // Step 1: Initialize state variables
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);
  const [content, setContent] = useState("");

  // Step 2: Handle text area content change
  const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value);
  };

  // Step 3: Handle form submission
  const onSubmit = async (data: string) => {
    try {
      if (data.trim() !== "") {
        setIsLoading(true);
        // Step 4: Content submission (will be replaced later)
        router.push("/");
      }
    } catch (error) {
      console.error(error);
    } finally {
      // Step 5: Reset state and loading indicator
      setContent("");
      setIsLoading(false);
    }
  };

  // Render the component
  return (
    <div className="p-6">
      <textarea
        className="w-full p-2 border rounded"
        rows={4}
        placeholder="Write your post..."
        value={content}
        onChange={handleContentChange}
      />
      <button
        className={`${
          isLoading ? "bg-gray-500" : "bg-blue-500 hover:bg-blue-600"
        } w-32 hover:cursor-pointer text-white font-bold py-2 px-4 rounded float-right`}
        onClick={() => onSubmit(content)}
        disabled={isLoading}
      >
        {isLoading ? "Posting..." : "Post"}
      </button>
    </div>
  );
}
```

The code works as follows:

**Step 1: Initialize state variables**

* Set up the state the component needs:
  + `router`: the Next.js router, used for navigation inside the app.
  + `isLoading`: records whether the component is loading, so the user experience stays smooth.
  + `content`: stores the text the user types for the post.

**Step 2: Handle text area content changes**

* The `handleContentChange` function runs whenever the user types in the text area. It writes the entered text into the `content` state.

**Step 3: Handle form submission**

* The asynchronous `onSubmit` function handles submitting the content. For now it only simulates a submission with `router.push('/')`. Swap this placeholder for your real submission mechanism, for example an API request.

**Step 4: Submit content**

* `router.push('/')` is a temporary stand-in for submitting content. After you connect the app to a backend server, replace it with your actual submission logic.

**Step 5: Reset state and loading indicator**

* Once a submission finishes, whether it succeeded or failed, `onSubmit` resets the component's state: it empties the `content` field and sets `isLoading` to `false` again.

### Rendering the `NewPost` component

* The `NewPost` component renders:
  + A text area where the user enters the post content.
  + A button that submits the post.
  + Visual feedback: the button's look and behavior change according to the `isLoading` state.

Add the component to your page like this:

src/app/new-post/page.tsx

```
import NewPost from "@/components/NewPost";

export default function NewPostPage() {
  return <NewPost />;
}
```

## User list page

### The `UserItem` component

Each user is shown by the `UserItem` component. It contains everything needed to display one user: their name and a control to follow or unfollow them.

src/components/UserItem.tsx

```
import { useState } from "react";

export type UsersDto = {
  name: string;
  id: Buffer;
};

export default function UserItem({ user }: { user: UsersDto }) {
  // Step 1: Initialize state variables
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [isFollowing, setIsFollowing] = useState<boolean>(false);

  // Step 2: Handle follow/unfollow click
  const handleFollowClick = async (userId: Buffer, follow: boolean) => {
    try {
      setIsLoading(true);
      // Step 3: Handle follow/unfollow logic (Will be replaced later)
      console.log("Following " + userId.toString("hex") + ": " + follow);
      setIsFollowing(follow);
    } catch (error) {
      console.log(error);
    } finally {
      // Step 4: Reset the loading indicator
      setIsLoading(false);
    }
  };
  // Render the component
  return (
    <div className="flex justify-between mb-4">
      <div className="flex items-center">
        {/* User Avatar or Image */}
        <div className="w-10 h-10 bg-gray-300 rounded-full mr-4 flex justify-center items-center">
          {user.name[0]}
        </div>
        <span className="text-lg font-semibold">{user.name}</span>
      </div>
      <button
        className={`${
          isLoading ? "bg-gray-500" : "bg-blue-500 hover:bg-blue-600"
        } w-32 hover:cursor-pointer text-white font-bold py-2 px-4 rounded float-right`}
        disabled={isLoading}
        onClick={() => handleFollowClick(user.id, !isFollowing)}
      >
        {isLoading ? "Loading..." : isFollowing ? "Following" : "Follow"}
      </button>
    </div>
  );
}
```

Here is what the component does:

**Step 1: Initialize state variables**

* The component keeps two pieces of state:
  + `isLoading`: shows whether a user-related operation, such as following or unfollowing, is still running.
  + `isFollowing`: shows whether the logged-in user follows the displayed user, so their follow status is visible right away.

**Step 2: Handle follow/unfollow interaction**

* `handleFollowClick` handles following and unfollowing. It runs when the user clicks the follow/unfollow button and sets `isLoading` to true while the operation is in progress. This is placeholder logic; replace it with real calls to your backend or database.

**Step 3: Update follow status**

* For now, `handleFollowClick` only logs the follow/unfollow action. Replace that with real data operations.

#### Rendering the `UserItem` component

* `UserItem` renders:
  + A user avatar or image: a circle containing the user's initials, so each user is easy to recognize.
  + A user name: shown prominently beside the avatar.
  + A follow/unfollow button: lets the viewer follow or unfollow the displayed user. Its look and behavior depend on the `isLoading` and `isFollowing` state variables, giving instant feedback.

### The `UsersList` component

The `UsersList` component collects several user items into one list. It renders the users dynamically with the `UserItem` component.

src/components/UserList.tsx

```
import UserItem, { UsersDto } from "./UserItem";

export type GetUsersReturnType = {
  pointer: number;
  users: UsersDto[];
};

export default function UsersList() {
  // Define an example array of users
  const users: GetUsersReturnType | undefined = {
    pointer: 0,
    users: [{ name: "User1", id: Buffer.from("AB", "hex") }],
  };

  return (
    <div className="p-4 md:p-8">
      <ul>
        {users && users.users.length > 0 ? (
          users.users.map((user, index) => (
            <li key={user.id.toString()}>
              {/* Render the UserItem component for each user */}
              <UserItem user={user} />
              {/* Add a horizontal line between user items */}
              {index < users.users.length - 1 && (
                <hr className="my-4 border-t border-gray-300" />
              )}
            </li>
          ))
        ) : (
          <></>
        )}
      </ul>
    </div>
  );
}
```

How it works:

* The `GetUsersReturnType` type describes the shape of user data, matching the format returned by the backend or database.
* The example uses a placeholder array of users. Replace it with real data from your system.
* The component maps over the user array and renders a `UserItem` for each entry, with horizontal lines between items to keep the list readable.

Last, add the component to the `users` page:

src/app/users/page.tsx

```
import UsersList from "@/components/UserList";

export default function UsersPage() {
  return <UsersList />;
}
```

These components give your app what it needs to handle user interactions and keep users engaged.

Written for this site from Chromia’s public course. Code samples are the course’s own.

[← Previous**Set up the project**](https://chromia.com/learn/my-news-feed/module-two/setup/)[Next →**Connect the client**](https://chromia.com/learn/my-news-feed/module-two/connecting-the-client/)

On this page

* [Overview](#overview)
* [Basic layout](#basic-layout)
* [Navigation](#navigation)
* [News feed (home) page](#news-feed-home-page)
* [New post page](#new-post-page)
* [User list page](#user-list-page)
