Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

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.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 45m
Published
Clock icon Apr 16, 2024

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. 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 and origData.json. The mockData 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 in mockData, simply paste origData into mockData.

    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.

  2. 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.

  3. 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. The public folder typically containing non-sensitive assets such as images and icons. All of your source code is contained within the app 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 the lib and components folders, which has been given to you for this lab. The lib folder contains utility functions, the JSON "database" files, and backend logic for you to use. The components 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 called navBar.tsx, itemList.tsx, and deleteItemForm.tsx. These files utilize the tsx file format instead of jsx 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 and Image are built-in Next.js components. Link extends the HTML a tag and is the primary method for navigation, while Image extends the HTML img tag and provides automatic optimizations. With these imported, now it's time to design the NavBar 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 bind id to the deleteItem 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 an id. When clicked, it will send this id to the server to perform the logic for item deletion using the provided id.


    ItemList

    Lastly, you will need to implement the ItemList component within itemList.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 with export default function ItemList({ jsonData }: {jsonData: Item[]} )

    The DeleteButton is the component you just built while Item 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 of Item, a type that you just imported. Now let's implement ItemList.

    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. 
  4. 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 as page.tsx, page.ts, page.jsx, or page.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 current page 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 a page.tsx and a layout.tsx file, which is always generated by default. This page.tsx represents your home page, while the layout 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 the mockData.json file in the lib 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 your NavBar component at the top with import 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 from ItemList.

    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.

  5. 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 own page files. Optionally, they can also contain their own layout files, their own set of utility files/folders, and even other routes(which are called nested). However, only the content within a routes page 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 and exampleSecond with their own page files, you would access them at example.com/exampleFirst or example.com/exampleSecond.

    The default app folder is a route. It's own page 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 a layout file for the application to function. This is because the layout in the app 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 a Link element that routes to '/addItem'. In order to setup this route, create a new folder called addItem in the app directory. Inside this folder, create a page.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 bind deleteItem in the deleteItemForm.tsx file. This is because the AddItem() component is not sending any additional data to the server besides the data from the form itself, whereas the DeleteButton component was sending additional data(the id 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, each Item has a Link element that navigates to /editItem/${item.id}. The problem is that the last segment of this URL path is an id, 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 the id parameter that is provided.

    To set this dynamic route up, create another folder in the app directory called editItem. Inside this editItem folder, create another folder called [id]. Your page.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 the page 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 the async keyword because the database variable uses await for the asynchronous function getData(). You have an id variable because the id parameter the route receives from the params object is a string, but since the id should be a number you cast it to a number using Typescripts Number() function. Defining the database variable as an Item[] is necessary because the callback function for findIndex() needs to know that each element is an Item type and thus contains an id field for comparison.

    The findIndex() method returns -1 if no item is found that meets the specified criteria, which is why you want the redirect('/') 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(the index of the item) to the server besides just the form data, updateItem needs to be bound to index similar to how you bound deleteItem in your deleteItemForm.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.

George is a Pluralsight Author working on content for Hands-On Experiences. He is experienced in the Python, JavaScript, Java, and most recently Rust domains.

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.