tRPC - Client Rendering
by Tomas TrescakΒ· tRPC

Root Folder
Not Attempted
NameProgress
Introduction
Not Read
🍰 Player
Not Read
Setup
Not Attempted
NameProgress
Software Installation
Not Read
Project Setup
Not Read
Running and Testing
Not Read
React and ReactDOM
Not Read
πŸ’‘ Assignment 1: Welcome Message
Not Attempted
Submissions
Not Read
React
Not Attempted
NameProgress
JSX and Components
Not Read
Props
Not Read
πŸ‘Ύ Exercise: Props
Not Attempted
CSS Styles
Not Read
useState and Hooks
Not Read
πŸ‘Ύ Exercise: useState
Not Attempted
Conditional Rendering
Not Read
Lists
Not Read
πŸ‘Ύ Exercise: Lists
Not Attempted
Forms and Events
Not Read
πŸ‘Ύ Exercise: Forms
Not Attempted
πŸ’‘ Assignment 2: Front End
Not Attempted
Advanced React
Not Attempted
NameProgress
Pure Components - memo
Not Read
Lifecycle - useEffect
Not Read
Expensive? - useMemo
Not Read
DOM Interactions - useRef
Not Read
forwardRef
Not Read
useImperativeHandle
Not Read
πŸ‘Ύ useImperativeHandle
Not Attempted
Context
Not Read
useCallback
Not Read
useId
Not Read
useReducer
Not Read
Infrastructure
Not Attempted
NameProgress
Database
Not Read
NextAuth and Github Authentication
Not Read
Prisma and ORM
Not Read
Project Setup
Not Read
Project Authentication
Not Read
APIs
Not Attempted
NameProgress
APIs
Not Read
APIs - Slides
Not Attempted
Rest APIs
Not Read
Rest APIs - Express.js
Not Read
ReastAPIs - Next.js
Not Read
Securing APIs
Not Read
Securing APIs - NextAuth
Not Read
tRPC
tRPC - Routers
tRPC - Server Rendering
tRPC - Client Rendering
Persisting Data
Assignment 3: APIs
0 / 50 XP

While server rendering is amazing, there are times when you will need to call a tRPC procedure from the client. There might be several reasons for that:

  1. You call a mutation with user data gathered on the client.
  2. You want to cache data on the client side and work with that data in several components
  3. You are often modifying data, and you do not want to invalidate the whole page after each modification

Let's explore how we can use RPC on the client.

Client tRPC

In this part, we will transform our src/components/task-list.tsx server component into a client component. Then, we will implement and use a new mutation on the client as well.

The client components contain β€œuse client” statement at the top of the file. Please add this statement to the src/components/task-list.tsx file and reload your page

"use client";

import { CreateTask } from "~/components/create-task";
...

Oh, Oh! A daunting error appears in your browser:

Build Error

We mentioned previously that asynchronous server components cannot be used client-side. Unfortunately, we cannot just remove the async statement from the component as we are using an asynchronous API call within it. Therefore, we need to remove all asynchronous calls. Also, we need to remove the server version of trpc api with react version, please adjust the following:

import { api } from "~/trpc/react"; // <- replaced server api with react api
import { useSession } from "next-auth/react"; 

export function TaskList() {
 
  const session = useSession();
  const tasksQuery = api.tasks.tasks.useQuery(undefined, {
    enabled: !!session.data,
  })

  if (tasksQuery.isLoading) {
    return <div>Loading tasks...</div>;
  }
  const tasks = tasksQuery.data ?? [];

Woooow, a lot of things happened here!

  1. We replaced the server API calls with react API calls using useQuery hook.
  2. We are now using react hooks to check if the user is logged in and also to execute a client-side query (behind the scenes tRPC uses the popular tanstack/query package)
  3. We are checking for a isLoading state in the query result and conditionally displaying a message
  4. We retrieve the data from the query and business as usual

Try to refresh your page. Another oh oh! You receieve an error:

Error: [next-auth]: `useSession` must be wrapped in a <SessionProvider />

Providers

React uses Context Providers to pass data to any child without needing to pass it explicitly via props. This allows any react component access, for example, signed-in user data or a tRPC API structure. Therefore, we need to add these providers to our application. We have prepared the SessionProvider and TRPCReactProvider for you in the src/app/providers.tsx file (open it and check it ou!). To use it, please modify the src/app/layout.tsx file as below:

import { Providers } from "./providers";

...

export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Please refresh you page and see the magic happening ;)

Mutations

Mutations will likely be executed from client components only, as they require data coming from user interactions. While React provides a very neat way of calling server functions directly, we still prefer to use tRPC for better control and enforce type validation using the zod library.

Here is an example of calling a tRPC mutation in the src/components/crate-task.tsx file. Please go line by line in the file to see if you understand.

"use client";

import { useQueryClient } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query";
import { useState } from "react";
import { api } from "~/trpc/react";
import styles from "./create-task.module.css";
import { type Task } from "./task-list";
// import { useRouter } from 'next/navigation';

export function CreateTask() {
  const [text, setText] = useState("");
  // this is the new mutation
  const createTask = api.tasks.addTask.useMutation();

  // we use the query client to update the data in the caches
  const queryClient = useQueryClient();
  
  // not used now, but would be used if you loaded the data using a server component
  // const router = useRouter();

  return (
    <>
      <input
        type="text"
        value={text}
        placeholder="What needs to be done?"
        className={styles.taskInput}
        onChange={(e) => setText(e.target.value)}
      />
      <button
        disabled={createTask.isPending}
        className={styles.taskButton}
        onClick={() => {
          // call the mutation
          createTask.mutate(
            {
              task: text
            },
            {
              onSuccess(data) {
                // update the cache on the client
                // 1. find the key by which the data is identified ion the cache
                const key = getQueryKey(api.tasks.tasks, undefined, "query");
                // 2. read the data in the cache
                const existing = queryClient.getQueryData<Task[]>(key) ?? [];
                // 3. write the new data to the cache
                queryClient.setQueryData(key, [...existing, data]);
                
                /*
                 * if you use Server Component to get your data, you do 
                 * not need to worry about this cache, but call
                 * router.refresh() instead 
                 */
              }
            }
          );
        }}
      >
        {createTask.isPending ? "Saving" : "Add Task"}
      </button>
    </>
  );
}
Maggie

Discuss with Maggie
Use the power of generative AI to interact with course content

Discussion

0 comments
Loading editor ...
Remember to be polite and report any undesirable behaviour

Category

Empty

Labels

Discussion has no labels

1 participant

user avatar

Priority

Notifications

You're not receiving notifications from this thread.
Course Outline