- Lab
- Core Tech

Guided: Working with React in a Next.js CRUD Application
In this lab, you will work with Next.js, an increasingly popular framework for developing React applications. You will utilize the fundamental concepts of React components and props, while also being introduced to core Next.js paradigms and practices. By the end of this lab, you will have built the frontend for an application that supports Create, Read, Update, and Delete (CRUD) operations.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Working with React in a Next.js CRUD Application lab.
You have been given an example application called Globoticket that already has most of its back-end logic implemented for you. Your goal for this lab is to complete the front-end implementation for this application and connect it to the predefined back-end logic to make the application fully functional. You are provided 2 JSON files called
mockData.json
andorigData.json
. ThemockData
file contains initial data and is meant to represent a "database" for storing data and changes made to it will persist across sessions. If you ever want to "reset" the data inmockData
, simply pasteorigData
intomockData
.To avoid too much exposition, you should at least have knowledge and/or experience with JavaScript and the fundamental concepts of JSX, Components, and Props in React. However, details on Next.js paradigms and design patterns will be given to provide context for your implementations in each step. By the end of this lab, you should be able to perform Create, Read, Update, and Delete(CRUD) operations in Globoticket.
There is a solution directory that you can refer to if you are stuck or want to check your implementations at any time. Keep in mind that the solution provided in this directory is not the only solution, so it is acceptable for you to have a different solution so long as the application functions as intended. This also applies to the Tailwind CSS stylings that are applied using
className
during each step.You can launch your application by entering
npm run dev
in the Terminal. The results of your code can be seen in the Web Browser tab. -
Challenge
Intro to Next.js
Overview
Next.js is a framework built on top of the React library. This allows developers to build the user interface of applications using the full capabilities of React while also giving them access to certain features exclusive to Next.
Next.js allows server side development through API routes/server actions, fetching data from databases, performing server side function calls, and rendering certain assets to reduce load times to the client. This means that Next.js can be considered a full-stack framework as it allows both front-end and back-end development within the same framework.
Starting a Next.js Project
You can easily start a Next.js project if you have Node installed. In your own environments, you would simply run the command
npx create-next-app@latest
on the command line and you will be given several prompts to answer with either a yes or no. Once answered, the command a bootstrap a fresh application for you. Some prompt options include whether you want to use Typescript, ESLint, or Tailwind CSS styling. Next.js typically ships these configurations by default unless specified otherwise.Aside from a few minor additions, the starter code for the Globoticket application in this lab has already been generated for you through
create-next-app
so you won't need to. It also has the aforementioned Typescript, ESLint, and Tailwind CSS configurations enabled.
Formats
Next.js applications typically have two formats that dictate their file structures and naming conventions. The two formats are the Pages router and the App router. The App router is the newer format and is the default format employed when creating new Next.js applications. The Pages format is the older Next.js format and is typically only seen in older applications, but it is still supported for backwards compatibility. The Globoticket application you will be working on is in the App router format.
-
Challenge
UI Components
Overview
Take a look at the filetree. Most of these top-level files are just configuration files and everything here is bootstrapped by default when running the
create-next-app
command. Thepublic
folder typically containing non-sensitive assets such as images and icons. All of your source code is contained within theapp
directory.Open the
app
directory to see all the current files for the Globoticket application. Again, everything here is generated by default with the exception of thelib
andcomponents
folders, which has been given to you for this lab. Thelib
folder contains utility functions, the JSON "database" files, and backend logic for you to use. Thecomponents
folder is currently empty, but you will be creating the necessary React components for Globoticket during this step.
NavBar
In the
components
folder, create 3 new files callednavBar.tsx
,itemList.tsx
, anddeleteItemForm.tsx
. These files utilize thetsx
file format instead ofjsx
since this project uses Typescript. Nonetheless, it functions mostly the same as the JSX syntax supported by React.Instructions (1)
Let's start with `navBar.tsx`. At the top of the file, you will need the following import statements: ``` import Link from "next/link";, import Image from "next/image"; import globoLogo from "@/public/globoticket-horizontal-white.svg"; ```Link
andImage
are built-in Next.js components.Link
extends the HTMLa
tag and is the primary method for navigation, whileImage
extends the HTMLimg
tag and provides automatic optimizations. With these imported, now it's time to design theNavBar
component.Instructions (2)
* After these import statements, define the component as `export default function NavBar() {}`. * In this component, render content using the `return()` statement * Inside the `return()` statement, define an HTML `nav` element and give it a `className` property of `"flex justify-between items-center mt-5"`. * Inside this `nav` element, define a `Link` element and give it an `href` property of `'/'`. This will redirect you to the homepage when clicked. Remember that JSX/TSX requires you to use curly braces to inject JS/TS code. * Inside this `Link` element, define an `Image` element and set its `src` property to `globoLogo`. Set its `alt` property to something along the lines of `"Globoticket Logo"` and give it a `width` of 300 and a `height` of 100. * Now exit this first `Link` element and define a second `Link` element within the `nav` element. Give it an `href` of `'/addItem'`. Give it `'bg-slate-700 font-bold p-2 hover:bg-slate-500'` for a `className` and set the text of the element to `Add Item`.This completes the
NavBar
component and when loaded, it will display the Globoticket logo on the far left and a button labelled Add Item on the far right. Clicking the logo will redirect you back to the home page. Clicking Add Item will currently redirect you to an error page, but in a later step you will add the functionality to redirect to another page that allows for you to create a new item.
DeleteButton
Now navigate to
deleteItemForm.tsx
.Instructions (1)
* At the top, you will need `import { deleteItem } from "@/app/lib/actions";`. This function is one of the provided server functions for deleting a specific item. You will need to link it to this component. * Define the component as `export default function DeleteButton({ id }: { id: number })`. The `{ id }: { id: number}` parameter is the *props* being received by the component from its parent. Since this is in Typescript, the type of `id` needs to be specified.The first instruction had you bind the
deleteItem()
function you imported to a variable. This is necessary because the component needs to send data, the id, to the server(deleteItem
), so the component must bindid
to thedeleteItem
function.Instructions (2)
* Create a `const` variable called `deleteItemById`. Set it to `deleteItem.bind(null, id);`. * Inside the `return()` statement, define a `form` element with an `action` prop set to `deleteItemById`. * Create a `button` element with a `className` property of `"p-2 bg-red-800 font-bold hover:bg-red-950"`. Use the text `Delete` for the button text.With this, you now have a
DeleteButton
component that receives anid
. When clicked, it will send thisid
to the server to perform the logic for item deletion using the providedid
.
ItemList
Lastly, you will need to implement the
ItemList
component withinitemList.tsx
. This is the component that will display all the items in the database.Instructions (1)
* At the top, you will need the following import statements:import Link from "next/link"; import DeleteButton from "@/app/components/deleteItemForm"; import { Item } from "@/app/lib/util";
- Define the
ItemList
component withexport default function ItemList({ jsonData }: {jsonData: Item[]} )
The
DeleteButton
is the component you just built whileItem
is a custom type that has been given to you to represent the items in the database. . As you may notice, this time the props received is an array ofItem
, a type that you just imported. Now let's implementItemList
.Instructions (2)
* Define a `return()` statement, create a fragment and inside the fragment escape into JSX using curly braces. ``` return( <> // Fragment { // Curly braces escapes into JSX/TSX for JS/TS statements} </> )
* Within the curly braces, use the JavaScript `map()` function on the `jsonData` variable. It should look something like `jsonData.map((item) => ( html element ));` * For your html element, create a `div` that has a `key` prop set to `item.id` and a `className` of `"p-4 my-5 border border-x-0 border-t-0 border-teal-400 flex justify-between items-center"` * Within this `div`, create 2 more `div` elements. * The first `div` should contain a `h2` header with a `className` of `"font-bold text-2xl"` and displays `item.title`. The `div` should also include a `p` element that displays `item.description` * The second `div` should have a `className` of `"flex gap-3"` and contain 2 elements: a `DeleteButton` and a `Link`. The `DeleteButton` should have an `id` prop of `item.id`. * The `Link` element should have an `href` of `` `/editItem/${item.id}` ``. It should also contain a `p` element with a `className` of `"p-2 font-bold bg-blue-500 hover:bg-blue-300"` containing the text `Edit`. </details> The usage of the fragments is necessary because React component can only return a "single" element. Since the `map()` function would generate a number of sibling elements, you need to enclose them within that `<> </>` fragment, otherwise you would get an error. This wasn't an issue in other components like `NavBar` or `DeleteButton` since all the sub-elements were already enclosed in a single element. When this component is loaded, you will be able to see a list of all the items in the database along with a button to delete or edit each item.
- Define the
-
Challenge
Pages and Layouts
Overview
All the necessary components should be finished, but currently you cannot see them in action yet. This has to do with how Next.js handles pages and layouts.
A page is the display/UI that users see when interacting with your web application and is always defined in a file called
page
. Extensions for this file may vary, such aspage.tsx
,page.ts
,page.jsx
, orpage.js
depending on the language you are working in. What users see and interact with in your web application is almost always defined in the currentpage
file that is loaded.A layout is a step above a page and typically just contains designs/components/assets that you would like to be present across multiple pages. A simple, though not entirely perfect, analogy would be like comparing it to a picture frame while the currently loaded page would be the picture held in the frame. Like pages, layouts are contained in
layout
files with the same file extension as the page.Functionally speaking, the code in these files is no different from how you would implement a normal React component. It's just Next.js convention to load the component in these specific files for display.
Home Page
Take a look at the
app
directory in the filetree. There is apage.tsx
and alayout.tsx
file, which is always generated by default. Thispage.tsx
represents your home page, while thelayout
is the root layout that dictates the structure of the web application.Instructions (1)
Let's start with `page.tsx`. At the top of the file, you will need the following import statements: ``` import ItemList from "@/app/components/itemList"; import { getData } from "@/app/lib/util"; ```The
ItemList
is the component you should have just implemented that displays all the items in the database.getData()
is a helper function provided to you that asynchronously retrieves the data from the database, aka themockData.json
file in thelib
directory.Instructions (2)
* Add the `async` keyword to the component. It should come right before the `function` keyword. * In the component body, define a `const` variable set to the result of `getData()`. Remember that `getData()` is asynchronous. * In the `return()` statement, define an `ItemList` component with the `jsonData` prop set to your `const` variable.With this, when your homepage is loaded it will retrieve the items in the database and provide them to the
ItemList
component to be displayed on the screen.
Root Layout
Move to
layout.tsx
. Import yourNavBar
component at the top withimport NavBar from "@/app/components/navBar";
and define it where the commented line is.With this completed, whenever you run your Globoticket application you should see a home page displaying all the items currently in the database. The image logo and Add Item button at the top are from your
NavBar
component while the list of items is fromItemList
.At this time, clicking the Delete button will properly delete items from the list, but clicking Add Item or Edit will do nothing. This is because the routes that these elements link to have not been setup yet, but you will rectify that in the next step.
-
Challenge
Routes
Overview
As mentioned in the previous step, a Next.js application can have multiple different pages for display to the user. To navigate between these pages, you need to employ routes. In Next.js, routes are defined as folders in the
app
directory that contain their ownpage
files. Optionally, they can also contain their ownlayout
files, their own set of utility files/folders, and even other routes(which are called nested). However, only the content within a routespage
file will be accessible to the user.Accessing a route involves adding the route folder name to the URL segment. For example if you had two route folders titled
exampleFirst
andexampleSecond
with their ownpage
files, you would access them atexample.com/exampleFirst
orexample.com/exampleSecond
.The default
app
folder is a route. It's ownpage
file represents the home page at with the'/'
segment(ie.example.com/
), but it is special in that it is the only route that requires alayout
file for the application to function. This is because thelayout
in theapp
directory is responsible for the structure of the entire application, which is why it's called a root layout.
Add Item
Let's get started with implementing the route to add a new item. If you recall in your
NavBar
component, there was aLink
element that routes to'/addItem'
. In order to setup this route, create a new folder calledaddItem
in theapp
directory. Inside this folder, create apage.tsx
file. Now you can implement the component for adding an item.Instructions (1)
* Use `import { createItem } from "@/app/lib/actions";` at the top of the file * Define the component with `export default function AddItem()` * In the `return()` statement, define a `form` element with its `action` set to `createItem` and a className of `"flex flex-col gap-3"` * Define 2 `input` elements and followed by a `button` element.Notice that you didn't need to bind
createItem
to anything here whereas you had to binddeleteItem
in thedeleteItemForm.tsx
file. This is because theAddItem()
component is not sending any additional data to the server besides the data from the form itself, whereas theDeleteButton
component was sending additional data(theid
it received as a parameter) to the server besides the form data.Instructions (2)
* The 2 `input` elements should have a `className` of `"border border-teal-400 px-8 py-2"`, a `type` of `"text"`, and the `required` attribute. * The first of the 2 `input` elements should have a `name` attribute of `"title"` and `placeholder` text as `"Item Title"`. * The second of the 2 `input` elements should have a `name` attribute of `"description"` and `placeholder` text as `"Item Description"`. * The `button` element should have the `className` of `"bg-teal-400 font-bold py-3 px-6 w-fit"` and contain the text `Add New Data`.After completing this component, clicking the Add Item button on the
NavBar
component in your application will redirect to another page that allows you add a new item to the database.
Edit Item
Lastly, you will need to implement the route for editing items. In your
ItemList
component, eachItem
has aLink
element that navigates to/editItem/${item.id}
. The problem is that the last segment of this URL path is anid
, which means that this value will be different depending on the item you click. To handle this, you will need a dynamic route because you do not know the exact route ahead of time; the route needs to adjust its content depending on theid
parameter that is provided.To set this dynamic route up, create another folder in the
app
directory callededitItem
. Inside thiseditItem
folder, create another folder called[id]
. Yourpage.tsx
file should go inside this[id]
folder. In Next.js, enclosing the name of the parameter in square brackets is the naming convention for dynamic routes. Now that the route is setup, it's time to implement thepage
file.Instructions (1)
* At the top of the file, import the following statements: ``` import { updateItem } from "@/app/lib/actions"; import { Item, getData } from "@/app/lib/util"; import { redirect } from "next/navigation"; ``` * Define the component with `export default async function EditItem({params} : {params: {id: string}})` * Create a `const` variable called `id` and use it to store `Number(params.id);`. * Create a `const` variable called `database` and give it a type of `Item[]`, for example `const database: Item[]`. Use this variable to store the result of `await getData();`. * Use `findIndex` on your `Item[]` variable to find an `Item` with the matching `id`. For instance, `const index = database.findIndex((item) => item.id == id);` * If `index` is -1, do a `redirect('/')`. You can optionally add a `console.log()` message as well. * Otherwise, retrieve the corresponding item from `database` using your `index` from `findIndex()`. * Bind `updateItem` which you imported to a `const` variable, such as `updateItemByIndex`, using `updateItem.bind(null, index)`.The
EditItem()
component you have defined has theasync
keyword because thedatabase
variable usesawait
for the asynchronous functiongetData()
. You have anid
variable because theid
parameter the route receives from theparams
object is a string, but since theid
should be a number you cast it to a number using TypescriptsNumber()
function. Defining thedatabase
variable as anItem[]
is necessary because the callback function forfindIndex()
needs to know that each element is anItem
type and thus contains anid
field for comparison.The
findIndex()
method returns -1 if no item is found that meets the specified criteria, which is why you want theredirect('/')
to take you back to the home page if that's the case. Otherwise, you want to retrieve the specified item to display its current values. Since you need to send additional data(theindex
of the item) to the server besides just the form data,updateItem
needs to be bound toindex
similar to how you bounddeleteItem
in yourdeleteItemForm.tsx
.Lastly, you need to create the
Form
element to be displayed on the page.Instructions (2)
* In the `return()` statement, define a `form` element with `action` set to `updateItemByIndex` and a `className` of `"flex flex-col gap-3"`. * Define a `p` element that displays the current title of the item followed by an `input` element * Define another `p` element that displays the current description of the item followed by an `input` element * Both `input` elements should have a `className` of `"border border-teal-400 px-8 py-2"` and a `type` attribute set to `"text"`. * The `input` element following the current title should have a `name` attribute of `"title"` and `placeholder` attribute of `"New Item Title"`. Do something similar for the `input` element for the item description. * Finally, define a `button` element with the `className` of `"bg-teal-400 font-bold py-3 px-6 w-fit"` and set its text to `Edit Item`.
Try it out
If you've completed all the implementations from these steps, your Globoticket application should be done. Run the application and you should see a list of items with their names and descriptions along with buttons to delete or edit each item. Clicking the Add Item or Edit buttons should redirect you to a different page to add or edit an existing item, while deleting items will delete them from the database and update the page accordingly. You can click the Globoticket image logo to return to the home page at any time.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.