Wow! How to easily introduce GlobalState with AppRouter (RSC) and share the state across SC, CC, and ServerActions.

vteacher

VTeacher

Posted on February 3, 2024

Wow! How to easily introduce GlobalState with AppRouter (RSC) and share the state across SC, CC, and ServerActions.

Hi,

I'm a front-end engineer and a back-end engineer.
I like choosing a combination of classic technology and slightly geeky technology.

More and more companies are adopting Next.js AppRouter (React Server Components)!
This time, we will introduce a method that we use at our company to share state across the server components (SC), client components (CC), and ServerActions!

Image description

In this way, we assume that you want to share the state from a client component at the end of one branch to a server component at another branch in an AppRouter (RSC) environment.
This is when you want to avoid a bucket brigade (chain of handoff from parent to child).

(Still... I'm a bucket brigade advocate...)

Introduction

There is a starter template for Next.js provided by Vercel.

Next.js starter templates and themes
Discover Next.js templates, starters, and themes to jumpstart your application or website build.

It's convenient 😳

This includes a demo that implements a chat UI similar to ChatGPT.
This time, we will introduce a global state to it (nextjs-ai-chatbot).

Next.js AI Chatbot
A full-featured, hackable Next.js AI chatbot built by Vercel

The skill sets and functions are as follows, and include the minimum elements necessary for general service development.
NextAuth.js is used for the sign-in/sign-out function.

- Next.js App Router
- React Server Components (RSCs), Suspense, and Server Actions
- Vercel AI SDK for streaming chat UI
- Support for OpenAI (default), Anthropic, Cohere, Hugging Face, or custom AI chat models and/or LangChain
-shadcn/ui
   - Styling with Tailwind CSS
   - Radix UI for headless component primitives
   - Icons from Phosphor Icons
- Chat History, rate limiting, and session storage with Vercel KV
- NextAuth.js for authentication
Enter fullscreen mode Exit fullscreen mode

Also, this is provided by the team at Vercel. In other words, it is the implementation code of the original people!

This library is created by Vercel and Next.js team members, with contributions from:
Jared Palmer (@jaredpalmer) - Vercel
Shu Ding (@shuding_) - Vercel
shadcn (@shadcn) - Vercel

local environment

Repository

Please import it using git clone or Fork.

Environment variables (.env)

If you want to see the code and actually see it working, you'll need a key to OpenAI and Vercel KV (free plan is fine). You will also need a GitHub account for the sign-in feature.

Create a .env file by copying .env.example.

*AUTH_SECRET is the value displayed when accessing https://generate-secret.vercel.app/32.

boot

Prepare the Node.js environment and install it.

*For pnpm

pnpm install
Enter fullscreen mode Exit fullscreen mode
pnpm dev
Enter fullscreen mode Exit fullscreen mode

open

You can sign in with your GitHub account. If you successfully sign in, the following screen will appear.

Image description

Enter your question in the text field labeled Send a message, click the Send button, and watch the OpenAI API reply back.

  • If you want to change the Large-Scale Language Model (LLM)

It is written to use gpt-3.5-turbo, so if you want to change to 4 series, rewrite app/api/chat/route.ts.

   const res = await openai.chat.completions.create({
     // model: 'gpt-3.5-turbo',
     // model: 'gpt-4',
     model: 'gpt-4-turbo-preview',
     messages,
     Temperature: 0.7,
     stream: true
   })
Enter fullscreen mode Exit fullscreen mode

*OpenAI API is a billing system, so please check the usage status on the Administration screen.

Folder structure

The folder structure of nextjs-ai-chatbot is as follows.

Project
├── app
│ ├── (chat)
│ │ ├── [id]
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ └── actions.ts '<--------- * Server Actions (use server)'
│
├── components '<------------ Component storage area'
│ ├── chat.tsx '<--------- * Client Component (use client)'
│ ├── sidebar-list.tsx '<-- * Server Component'
│ └── prompt-form.tsx '<--- * Client Component (unmarked)'
│
├── lib
│ └── hooks '<------------- Custom hook storage area'
├── middleware.ts
└── public '<---------------- Resource file storage area'
Enter fullscreen mode Exit fullscreen mode

Although it is not directly related to this demo, you will want to take advantage of middleware, which is very convenient when using Vercel.
By the way, regarding the part marked "unmarked", even if use client is not explicitly stated, the caller (ancestor) is a client.

side, the code effectively functions as a ClientComponent as well. If you are not planning on using it like SharedComponent, it is better to clearly write use client at the beginning.

Introduce global state

Will begin the main subject. Let's introduce global state to a project configured with this RSC (Next.js AppRouter).

Due to the nature of RSC, traditional global state management strategies such as Recoil cannot be applied. Therefore, it is necessary to use a global state management library specialized for RSC.
This time, we will use nrstate to implement a global state.

https://www.npmjs.com/package/nrstate

nrstate represents the following elements
-N:Next.js

  • R: React Server Components
  • State: State management

This is a global state management library specialized for Next.js' AppRouter.

Install nrstate

*For pnpm

pnpm install nrstate --save-dev
Enter fullscreen mode Exit fullscreen mode
pnpm install nrstate-client --save-dev
Enter fullscreen mode Exit fullscreen mode

Determine the state of the entire page

nrstate has a concept called PageState. This means considering the state of the entire page. This allows you to retrieve state across components even if the page is made up of multiple components.

In nextjs-ai-chatbot, I think it is appropriate to treat the only value input by the user (message) as a global state, so I will use this as PageState.

Image description

  • Official site

  • points

    • Establish global state considering the entire page (applies across multiple components).
    • Setting (setting) global state is only possible on the client side.
    • Global state can be retrieved on the client side or server side.
    • Refresh processing is done automatically.
    • The basic principle of RSC is that it starts with the server component.

Now let's implement it.

Create PageState

Define PageState by creating a file like this:

  • app/PageStateChat.tsx (*Create a new one)
export type PageStateChat = {
     message: string
}

export const initialPageStateChat = {
     message:''
} satisfies PageStateChat

export const pathChat = '/'
Enter fullscreen mode Exit fullscreen mode
  • PageStateChat defines the type of this PageState.
  • initialPageStateChat specifies the default value.
  • pathChat indicates the target path, and this time it is set to the root (/). This allows PageState to be referenced everywhere under the root.

Implement a provider for PageState.

Since there is no page.tsx in the root of nextjs-ai-chatbot, implement PageStateProvider in layout.tsx as an alternative.

  • Be sure to implement PageStateProvider in a server component (I think page.tsx and layout.tsx are usually made into server components, so I don't think there will be any problems...)

  • app/layout.tsx

import.

import { currentPageState } from 'nrstate/PageStateServer'
import PageStateProvider from 'nrstate-client/PageStateProvider'
import {
   PageStateChat,
   initialPageStateChat,
   pathChat
} from '@/app/PageStateChat'
Enter fullscreen mode Exit fullscreen mode

For render, just surround the existing code with PageStateProvider.

export default function RootLayout({ children }: RootLayoutProps) {
   return (

     <PageStateProvider
       current={currentPageState<PageStateChat>(initialPageStateChat, pathChat)}
     >
       <html lang="en" suppressHydrationWarning>
         ...(omitted)...
       </html>
     </PageStateProvider>

   )
}
Enter fullscreen mode Exit fullscreen mode

This completes the setup. It's easy.

Set the value for PageState

  • The area under components is where general-purpose components are placed, but this time we will modify it for convenience.

ClientComponent

PageState can only be set from client components (CC).
Update PageState using the change event of the defined global state message.

  • components/prompt-form.tsx

prompt-form.tsx does not have use client written at the beginning, but its caller (ancestor) is a client component (CC), so it is running as a client component.

Import PageState.

import { usePageState } from 'nrstate-client/PageStateClient'
import { PageStateChat, pathChat } from '@/app/PageStateChat'
Enter fullscreen mode Exit fullscreen mode

Use the PageState hook.

export function PromptForm({...(omitted)...}) {
   const [pageState, setPageState] = usePageState<PageStateChat>()

   ...(omitted)...
}
Enter fullscreen mode Exit fullscreen mode

After inputting, when you click the submit button (onSubmit event), update PageState using the input value (input).

     <form
       onSubmit={async e => {
         ...(omitted)...

         setPageState(
           {
             ...pageState,
             message: input
           },
           pathChat
         )

...(omitted)...
Enter fullscreen mode Exit fullscreen mode

Get value from PageState

ClientComponent

  • components/chat.tsx

Import PageState.

import { usePageState } from 'nrstate-client/PageStateClient'
import { PageStateChat, pathChat } from '@/app/PageStateChat'
Enter fullscreen mode Exit fullscreen mode

You can get the PageState (message) using the PageState hook.

export function Chat(...(omitted)...) {

   const [pageState] = usePageState<PageStateChat>()
   const { message } = pageState

   ...(omitted)...
}
Enter fullscreen mode Exit fullscreen mode

Let's display the global state on the screen for debugging.

<p className="text-sm text-blue-500">
   ClientComponent(use client): message={message}
</p>
Enter fullscreen mode Exit fullscreen mode

ServerComponent

Next is the server side.
The value of nrstate can only be changed on the client side, and the value can only be referenced on the server side.

  • components/sidebar-list.tsx

Import PageState.
*On the server side, use getPageState.

import { getPageState } from 'nrstate/PageStateServer'
import {
   PageStateChat,
   initialPageStateChat,
   pathChat
} from '@/app/PageStateChat'
Enter fullscreen mode Exit fullscreen mode

Get the PageState using the PageState hook.

export async function SidebarList(...(omitted)...) {

   const pageState = getPageState<PageStateChat>(initialPageStateChat, pathChat)
   const { message } = pageState

   console.log('ServerComponent', message)
   ...(omitted)...
}
Enter fullscreen mode Exit fullscreen mode

Displays PageState on screen for debugging.

<p className="text-sm text-orange-500">
   ServerComponent message={message}
</p>
Enter fullscreen mode Exit fullscreen mode

ServerActions

ServerActions is also a server-side function, so we treat it in a similar way to server components (SCs).

  • app/actions.ts

Import PageState.
On the server side, use getPageState.

import { getPageState } from 'nrstate/PageStateServer'
import {
   PageStateChat,
   initialPageStateChat,
   pathChat
} from '@/app/PageStateChat'
Enter fullscreen mode Exit fullscreen mode

Get the PageState using the PageState hook.

export async function removeChat(...(omitted)...) {

   const pageState = getPageState<PageStateChat>(initialPageStateChat, pathChat)
   const { message } = pageState

   console.log('ServerActions(use server) message=', message)
   ...(omitted)...
Enter fullscreen mode Exit fullscreen mode

*There is an original bug in nextjs-ai-chatbot on line 66, so please fix it as follows.

   if (String(uid) !== session?.user?.id) {
     ...(omitted)...
   }
Enter fullscreen mode Exit fullscreen mode

Explicitly remove PageState

If you want to explicitly remove PageState, do the following:
(possible with client component)

import { clearPageState } from 'nrstate-client/PageStateClient'
import { pathChat } from '@/app/demo/PageStateChat'
Enter fullscreen mode Exit fullscreen mode
clearPageState(pathChat)
Enter fullscreen mode Exit fullscreen mode

Operation confirmation

Now, let's try out the chat function!

As shown in the following video, the global state (value of PageState) can be shared across server components, client components, and ServerActions.

https://www.youtube.com/watch?v=Lzyxijqf6Ik

*For cases where debug information cannot be displayed on the screen, mainly server-side logs, please check from the terminal.

lastly

Here is the current repository.

💖 💪 🙅 🚩
vteacher
VTeacher

Posted on February 3, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related