This series of articles focuses on building a full-stack app with the following technology stack: PlanetScale - Prisma - tRPC - React. The project name is mythica and it will allow users to collect random mythical creatures. Here is the full list of the articles:
- Part One
- Part Two
- Part Three
- Part Four <— you are here
Table of contents
Open Table of contents
Starting React Project
1. Build Tool
I will be using vite to build the React application, although, this is not mandatory so feel free to use any build tool you prefer.
So simply re-create the client directory by starting a new React-typescript project inside the packages
folder.
npm create vite@latest client --template react-ts
2. Dependancies
After vite
creates the app, make sure to install the necessary dependencies. Your dependencies
section in your client
’s package.json
should be similar to this.
"dependencies": {
"@emotion/react": "^11.11.0",
"@mantine/core": "^6.0.11",
"@mantine/hooks": "^6.0.11",
"@tabler/icons-react": "^2.20.0",
"@tanstack/react-query": "^4.29.7",
"@trpc/client": "^10.27.1",
"@trpc/react-query": "^10.27.1",
"@trpc/server": "^10.27.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
"server": "^1.0.0",
"zod": "^3.21.4"
}
Please note that the mandatory ones are @tanstack/react-query, @trpc/client, @trpc/react-query, server and zod. I use mantine as the UI library with TablerIcons but feel free to use whatever you prefer. It is important to notice that server dependancy is indeed the local server package we have in the monorepo. You can simply install it by running the following: npm i server —workspace=client.
App Setup
1. Create a tRPC client
Inside src
create a new directory lib
which will contain a single file trpc.ts
which will create a trpc
instance to React that exposes the AppRouter
from our server
package we created earlier in one of the previous articles.
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "server";
export const trpc = createTRPCReact<AppRouter>();
2. Link trpcClient
to react-query
and provide it to the app
We will start by replacing the code we have in App.tsx
with the following, and we will explain it later:
import { useState } from "react";
import { trpc } from "./lib/trpc";
import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/react-query";
import { RandomCreature } from "./Pages/RandomCreature";
import {
ColorScheme,
ColorSchemeProvider,
Flex,
MantineProvider,
} from "@mantine/core";
import { SwitchToggle } from "./Components";
const BACKEND_URL = "http://localhost:3000/mythica";
function App() {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: BACKEND_URL,
}),
],
})
);
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"));
return (
<MantineProvider theme={{ colorScheme }} withGlobalStyles withNormalizeCSS>
<ColorSchemeProvider
colorScheme={colorScheme}
toggleColorScheme={toggleColorScheme}
>
<trpc.Provider queryClient={queryClient} client={trpcClient}>
<QueryClientProvider client={queryClient}>
<Flex
mih={50}
my={-500}
gap="lg"
align="start"
justify="space-between"
direction="column"
wrap="wrap"
mb={100}
>
<SwitchToggle />
<RandomCreature />
</Flex>
</QueryClientProvider>
</trpc.Provider>
</ColorSchemeProvider>
</MantineProvider>
);
}
export default App;
We will ignore theme-related stuff. The important bits here are that we obtain a QueryClient
from react-query
and start a trpc
client providing it with our server url. Later, when we return the JSX we wrap the entire app with trpc.Provider
and QueryClientProvider
; this way we can execute the procedures we have on the server from any component in our React app. Notice how the linkage is done between the two. trpc.Provider
takes in queryClient
from react-query
. This way when we use our trpc
client from any component we will get the benefits of react-query
refetch and caching API.
<trpc.Provider queryClient={queryClient} client={trpcClient}>
<QueryClientProvider client={queryClient}>...</QueryClientProvider>
</trpc.Provider>
Using getRandom
and getDetails
procedures from React
In the previous article we implemented two procedures: getRandom
and getDetails
. The goal here is that users will be presented with random creatures each time they visit the application and can view more details on this random creature by hovering over a button. Now we will create two React components: RandomCreature
and CreatureDetails
which will consume each of the mentioned procedures, respectively.
1. RandomCreature
Component
The entire code for this component can be found here, the following is a simplified version to showcase the important bits that relates to this article
export function RandomCreature() {
const response = trpc.creature.getRandom.useQuery();
const { classes } = useStyles();
if (response.isLoading || response.isRefetching) {
return <AppLoader />;
}
if (response.isError || !response.data?.creature) {
return <AsyncError />;
}
const creature = response.data?.creature;
return <>... // build your ui in case there is data</>;
}
As you can see, it is that simple to load data from a procedure inside a component with trpc
and react-query
. Notice that we have create AppLoader
and AsyncError
under the Components
folder to view an appropriate UI depending in the response. We also provide our AppLoader
as fallback
value which will be displayed until the data loads, or when we try to re-fetch the data by clicking the skip icon, but this bit will be explained in details in the next article.
2. CreatureDetails
Component
import {
HoverCard,
Avatar,
Text,
Group,
Anchor,
Stack,
Button,
Center,
Badge,
} from '@mantine/core';
import { trpc } from '../lib/trpc';
import { AppLoader, AsyncError } from '../Components';
interface CreatureDetailsProps {
id: number;
}
export function CreatureDetails(props: CreatureDetailsProps) {
const { id } = props;
const response = trpc.creature.getDetails.useQuery({ id: id });
const details = response?.data;
if (response.isLoading) {
return <AppLoader />;
}
if (response.isError || !details) {
return <AsyncError />;
}
return (
// jsx
);
}
It is worth mentioning here, the only difference in this component is that it depends on the id
from the currently displayed random creature as we can see from the function argument. We also do not need to do refetching here per our game logic which will be explained in details in the next article, so that is why we omit response.isRefetching
.
Similarly, CreatureDetails
component uses the same approach. How
Conclusion
In this tutorial, we have learned how to consume trpc
procedures from within a React application while using react-query
caching and refecthing capabilities. Next step, we will be describing our game logic as users will be able to collect creatures in a certain way in order to win the game.