# Connect the client · A simple Chromia app with Rell and React · Learn Chromia

> This lesson connects the frontend to the blockchain with the postchain-client library and the FT4-wrapper. You build a ContextProvider and write custom…

Source: https://chromia.com/learn/my-news-feed/module-two/connecting-the-client/

A simple Chromia app with Rell and ReactLesson 25 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/)

25 / 27Module 2: The React projectBeginner

# Connect the client

This lesson connects the frontend to the blockchain with the [postchain-client](https://www.npmjs.com/package/postchain-client) library and the [FT4-wrapper](https://www.npmjs.com/package/@chromia/ft4). You build a [ContextProvider](https://react.dev/learn/passing-data-deeply-with-context) and write custom [React hooks](https://react.dev/learn/reusing-logic-with-custom-hooks#extracting-your-own-custom-hook-from-a-component) so the app's components can work with the blockchain directly.

## Create a context

Blockchain data and state belong in a single shared context that every part of the app can reach. Add a file called `ContextProvider.tsx` to the `src/components` directory and declare the context there:

src/components/ContextProvider.tsx

```
"use client";

import {
  Session,
  createKeyStoreInteractor,
  createSingleSigAuthDescriptorRegistration,
  createWeb3ProviderEvmKeyStore,
  hours,
  registerAccount,
  registrationStrategy,
  ttlLoginRule,
} from "@chromia/ft4";
import { createClient } from "postchain-client";
import { ReactNode, createContext, useContext, useEffect, useState } from "react";
import { getRandomUserName } from "../app/user";

// Create context for Chromia session
const ChromiaContext = createContext<Session | undefined>(undefined);

export function ContextProvider({ children }: { children: ReactNode }) {
  // Initialize session and EVM address states
  const [session, setSession] = useState<Session | undefined>(undefined);

  // Additional state initialization will be defined here

  return <ChromiaContext.Provider value={session}>{children}</ChromiaContext.Provider>;
}
```

The `ContextProvider` component defines one context:

1. **ChromiaContext**: stores a `Session` object, which wraps the FT4 client. It tracks the current session's state, including the key pairs used to sign transactions.

The component sets up the initial state and wraps the app's components in the context. To make the context available, wrap the `NavBar` and `{children}` tags in `src/app/layout.tsx`:

src/app/layout.tsx

```
import { ContextProvider } from '@/components/ContextProvider'

<ContextProvider>
  <NavBar />
  {children}
</ContextProvider>
```

## Initialize the session

The flow diagram below shows how a new session is set up:

The steps work as follows:

1. **Start the app**: The app begins by connecting to MetaMask, a widely used Ethereum wallet provider.
2. **Check whether the user exists in the dapp**: The app checks if the user's Ethereum wallet is already linked to an FT account in the decentralized app (dapp).
3. **Create a session (existing user)**: When the wallet is linked to an account, the app opens a new session, through which the user interacts with the FT protocol.
4. **Create an account (new user)**: When no account is linked to the wallet, the app creates one. [Module 1](https://chromia.com/learn/my-news-feed/module-one/register-evm-accounts/register-evm-accounts/) covers this process in detail.
5. **Create a session**: Once the account exists, the app opens a session so the user can interact with the blockchain through the FT4-wrapper.

Next, implement this flow:

src/components/ContextProvider.tsx

```
// 2.
declare global {
  interface Window {
    ethereum: any;
  }
}

export function ContextProvider({ children }: { children: ReactNode }) {
  const [session, setSession] = useState<Session | undefined>(undefined);

  useEffect(() => {
    const initSession = async () => {
      console.log("Initializing Session");
      // 1. Initialize Client
      const client = await createClient({
        nodeUrlPool: "http://localhost:7740",
         blockchainRid: "26A69CAACE069D03404D58E17CF9E38B4417274D5E4BEB663E6F329FD56F6D90", // Add your blockchainRid
      });

      // 2. Connect with MetaMask
      const evmKeyStore = await createWeb3ProviderEvmKeyStore(window.ethereum);

      // 3. Get all accounts associated with evm address
      const evmKeyStoreInteractor = createKeyStoreInteractor(client, evmKeyStore);
      const accounts = await evmKeyStoreInteractor.getAccounts();

      if (accounts.length > 0) {
        // 4. Start a new session
        const { session } = await evmKeyStoreInteractor.login({
          accountId: accounts[0].id,
          config: {
            rules: ttlLoginRule(hours(2)),
            flags: ["MySession"],
          },
        });
        setSession(session);
      } else {
        // 5. Create a new account by signing a message using metamask
        const authDescriptor = createSingleSigAuthDescriptorRegistration(["A", "T"], evmKeyStore.id);
        const { session } = await registerAccount(
          client,
          evmKeyStore,
          registrationStrategy.open(authDescriptor, {
            config: {
              rules: ttlLoginRule(hours(2)),
              flags: ["MySession"],
            },
          }),
          {
            name: "register_user",
            args: [getRandomUserName()],
          }
        );
        setSession(session);
      }
      console.log("Session initialized");
    };

    initSession().catch(console.error);
  }, []);

  return <ChromiaContext.Provider value={session}>{children}</ChromiaContext.Provider>;
}
```

Here is what the code does:

1. **Initialize the client**: The client connects to the local blockchain node with `blockchainIid` 0. Running `chr node start` to launch a test node prints logs that show the REST API port, along with the blockchain's internal identifier (chain id) and its external identifier (blockchain Rid).

   The internal Id (`blockchainIid`) is the easier choice while testing, because it matches the chain's state. When you connect to a blockchain deployed on a network, use its referential Id (`blockchainRid`) instead, so the client targets and talks to the correct blockchain on that network.

   To switch from `blockchainRid` to `blockchainIid` for local development, follow the code below:

   typescript

   ```
   const client = await createClient({
     nodeUrlPool: "http://localhost:7740",
     // blockchainRid: "..."  // Replace this
     blockchainIid: 0          // With this for local dev
   });
   ```
2. **Connect with MetaMask**: The app connects to MetaMask. The `ethereum` object is declared globally on the `Window` interface so the compiler does not emit warnings.
3. **Retrieve accounts**: The app fetches every account in the dapp tied to the Ethereum wallet address. Each wallet is expected to have either 0 or 1 account.
4. **Start a new session**: If an account is found, the app starts a session using the "MySession" flag from the dapp's authentication handlers. The session expires after two hours, at which point the user must sign in again.
5. **Create a new account**: If no account is found, the user signs a message in MetaMask and the app sends a transaction to the dapp to create one. The [Conceptual design](https://chromia.com/learn/my-news-feed/module-one/register-evm-accounts/register-evm-accounts/#conceptual-design) page explains this in depth. The EVM key gets the "A" and "T" flags, which let it sign administrative and transfer operations, plus the "MySession" flag for the session key that the `Session` uses. `register_user` is set as the registration operation, which lets you pass the user name as an argument.

   The account's user name comes from `getRandomUserName`, a function defined in `src/app/user.tsx` that generates a random name.

src/app/user.tsx

```
const funnyAnimalNames = [
  "SneakyLlama",
  "CheekyMonkey",
  "LaughingPenguin",
  "CrazyKangaroo",
  "GigglingHedgehog",
  "WackyWalrus",
  "DancingDolphin",
  "BumblingBee",
  "HoppingHare",
  "SingingSeagull",
];

export function getRandomUserName() {
  const randomIndex = Math.floor(Math.random() * funnyAnimalNames.length);
  return funnyAnimalNames[randomIndex];
}
```

## Access context with custom hooks

Custom hooks make it simple to call backend queries and operations from the frontend:

src/components/ContextProvider.tsx

```
// Define hooks for accessing context
export function useSessionContext() {
  return useContext(ChromiaContext);
}
```

Then create a file named `src/app/hooks.tsx` and add a hook called `useQuery`:

src/app/hooks.tsx

```
// Create a custom hook for queries
import { useSessionContext } from "@/components/ContextProvider";
import { useEffect, useState, useCallback } from "react";
import { RawGtv, DictPair } from "postchain-client";

// Custom hook for queries and operations
export function useQuery<TReturn extends RawGtv, TArgs extends DictPair | undefined = DictPair>(
  name: string,
  args?: TArgs
) {
  const session = useSessionContext();
  const [serializedArgs, setSerializedArgs] = useState(JSON.stringify(args));
  const [data, setData] = useState<TReturn | undefined>();

  // Function to send the query
  const sendQuery = useCallback(async () => {
    if (!session || !args) return;
    const data = await session.query<TReturn>({ name: name, args: args });
    setSerializedArgs(JSON.stringify(args));
    setData(data!!);
  }, [session, name, args]);

  // Trigger the query when session, query name, or arguments change
  useEffect(() => {
    sendQuery().catch(console.error);
  }, [session, name, serializedArgs]);

  // Return query result and reload function
  return {
    result: data,
    reload: sendQuery,
  };
}
```

The hook reads the required context and runs the query again whenever the session, the query name, or the arguments change. Serializing the arguments keeps them stable and prevents unwanted queries. If the arguments are not yet initialized, the hook waits to run the query until they are defined.

## Implement the news feed

The news feed lives in the `NewsFeed.tsx` component. Start by defining Data Transfer Object (DTO) structures that match the return type of the `get_posts` query, so the data has a clear shape:

src/components/NewsFeed.tsx

```
import { useEffect } from "react";
import { useSessionContext } from "@/components/ContextProvider";
import { useQuery } from "@/app/hooks";

export type User = {
  name: string;
  id: number;
  account: number;
};
export type PostDto = {
  timestamp: number;
  user: User;
  content: string;
};
export type GetPostsReturnType = {
  pointer: number;
  posts: PostDto[];
};
```

Inside the `NewsFeed` component, call the `useQuery` hook to load the user's details, follower and following counts, and the feed's posts:

src/components/NewsFeed.tsx

```
export default function NewsFeed() {
    const session = useSessionContext()
    const accountId = session?.account.id;
    const { result: userName } = useQuery<string>("get_user_name", accountId ? { user_id: accountId } : undefined);
    const { result: followersCount } = useQuery<number>("get_followers_count", accountId ? { user_id: accountId } : undefined);

    const { result: followingCount } = useQuery<number>("get_following_count", accountId ? { user_id: accountId } : undefined);
    const { result: newsFeed, reload: reloadPosts } = useQuery<GetPostsReturnType>("get_posts", accountId ? { user_id: accountId, n_posts: 10, pointer: 0 } : undefined);

    // Refresh posts every 10 seconds
    useEffect(() => {
        const refreshPosts = setInterval(() => {
            reloadPosts();
        }, 10000);
        return () => {
            clearInterval(refreshPosts);
        }
    });
```

These calls use the `get_user_name`, `get_followers_count`, `get_following_count`, and
`get_posts` queries. Posts reload every 10 seconds. While `evmContext` is not
yet initialized, you pass `undefined` so loading proceeds cleanly. Add the values to the layout like this:

src/components/NewsFeed.tsx

```
return (
  <div className="p-4 md:p-8">
    <div className="flex justify-between items-center">
      <h1 className="text-2xl font-bold">{userName}</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">{followersCount}</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">{followingCount}</p>
        </div>
      </div>
    </div>

    {/* News Feed */}
    <div className="bg-white p-4 rounded-lg shadow">
      <ul>
        {newsFeed ? (
          newsFeed.posts.map((post, index) => (
            <li key={index} className="mb-4">
              <div className="flex">
                <div className="font-semibold">{post.user.name}</div>
                <div className="text-gray-500 text-sm ml-2">{new Date(post.timestamp).toLocaleString()}</div>
              </div>
              <div className="mt-2">{post.content}</div>
              {/* Add a horizontal line between posts */}
              <hr className="my-4 border-t border-gray-300" />
            </li>
          ))
        ) : (
          <p>Loading...</p>
        )}
      </ul>
    </div>
  </div>
);
```

### The `UserList` component

Add the following code to the `UserList` component to load 100 users:

src/components/UserList.tsx

```
import { useQuery } from "@/app/hooks";

export default function UsersList() {
    const { result: users } = useQuery<GetUsersReturnType>("get_users", { n_users: 100, pointer: 0 });
```

The app now fetches the first 100 users. You could extend the `get_users` and `get_posts` queries to load more users as you scroll, but this course keeps things simple.

### The `UserItem` component

For the follow/unfollow button in the `UserItem` component to work, you need to know whether you already follow the user, then call `follow_user` or `unfollow_user` accordingly. Starting from the structure built in [Lesson 2](https://chromia.com/learn/my-news-feed/module-two/scaffold/#user-list-page), add a hook for the session and call the matching operation.

src/components/UserItem.tsx

```
export default function UserItem({ user }: { user: UsersDto }) {
    // Step 1: Initialize state variables
    const session = useSessionContext();
    const accountId = session?.account.id;
    const { result: isFollowing, reload: updateIsFollowing } = useQuery<boolean>("is_following", accountId ? { my_id: accountId, your_id: user.id } : undefined);
  ...
    // Step 2: Handle follow/unfollow click
  const handleFollowClick = async (userId: Buffer, follow: boolean) => {
    if (!session) return
    try {
      setIsLoading(true);
      // Step 3: Handle follow/unfollow logic
      await session.call({
        name: follow ? "follow_user" : "unfollow_user",
        args: [userId]
      });
      updateIsFollowing();
```

The `isFollowing` state is replaced by a query that returns the real follow status. The `handleFollowClick` function calls `follow_user` or `unfollow_user` to toggle that status, and then reloads the query to refresh `isFollowing`, so the component always shows the current follow state.

### The `NewPost` component

Post creation follows the same pattern as `UserItem`. Using the scaffold from [Lesson 2](https://chromia.com/learn/my-news-feed/module-two/scaffold/#new-post-page), add this logic:

src/components/NewPost.tsx

```
export default function NewPost() {
  // Step 1: Initialize state variables
  const session = useSessionContext();
  ...
    // Step 3: Handle form submission
  const onSubmit = async (data: string) => {
    if (!session) return
    try {
      if (data.trim() !== '') {
        setIsLoading(true);
        // Step 4: Content submission
        await session.call({
          name: "make_post",
          args: [data],
        })
        router.push('/');
```

Your dapp now lets users write and publish posts.

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

[← Previous**Project scaffold**](https://chromia.com/learn/my-news-feed/module-two/scaffold/)[Next →**Summary and manual testing**](https://chromia.com/learn/my-news-feed/module-two/summary-and-tests/)

On this page

* [Create a context](#create-a-context)
* [Initialize the session](#initialize-the-session)
* [Access context with custom hooks](#access-context-with-custom-hooks)
* [Implement the news feed](#implement-the-news-feed)
