Friday, March 28, 2025

Let’s Build a Full-Stack App Using the MERN Stack! Part 3: React NextJS UI

Programming LanguageLet's Build a Full-Stack App Using the MERN Stack! Part 3: React NextJS UI


Now that we have the database and backend set up, let’s create the UI. We’re using the MERN stack, where “R” stands for React. We’ll be using Next.js, a React framework that extends React’s capabilities.

Setting Up Next.js

To create a Next.js application, run the following command:
npx create-next-app@latest
You’ll be prompted with several configuration choices:

  • Ok to proceed? → y
  • What is your project named? → ui
  • Would you like to use TypeScript? → Yes
  • Would you like to use ESLint? → Yes
  • Would you like to use Tailwind CSS? → Yes
  • Would you like your code inside a src/ directory? → No
  • Would you like to use App Router? → Yes
  • Would you like to use Turbopack for next dev? → No
  • Would you like to customize the import alias (@/* by default)? → No

Start the Development Server

Navigate into the ui folder and run the app:

cd ui  
npm run dev 
Enter fullscreen mode

Exit fullscreen mode

Install Axios

Next, install Axios to handle API requests:
npm install axios

Clean Up page.tsx

Delete everything inside page.tsx, so it looks like this:

export default function Home() 
  return (
    <div>
      <h1>Things</h1>
    </div>
  );

Enter fullscreen mode

Exit fullscreen mode

Now, let’s set next.js to ‘use client’, add imports, and create our interfaces. In Next.js 13, components are server components by default. Server components are rendered on the server and sent as static HTML to the client. They are great for performance and SEO but cannot use client-side features such as react hooks.

'use client'

import  useEffect, useState  from 'react';
import axios from 'axios';

interface Thing 
  _id: string;
  title: string;
  description: string;

Enter fullscreen mode

Exit fullscreen mode

Now, add the following methods to the default Home function.

const [things, setThings] = useState<Thing[]>([]);
const [selectedThing, setSelectedThing] = useState<Thing | null>(null);

// Base API endpoint
const API_BASE_URL = 'http://localhost:5000/things';

useEffect(() => 
  getTasks();
, []);

// Handles radio button click and populates the form with the selected thing's data
const handleRadioClick = (thing: Thing) => 
  setSelectedThing(thing);
  populateForm(thing);
;

// Populates the form fields with the data of the selected thing
const populateForm = (thing: Thing) => 
  const form = document.querySelector('form');
  if (form) 
    (form.querySelector('input[name="title"]') as HTMLInputElement).value = thing.title;
    (form.querySelector('input[name="description"]') as HTMLInputElement).value = thing.description;
  
;

// Clears the selected thing and resets the form and radio button selection
const clearSelectedThing = () => 
  setSelectedThing(null);
  clearForm();
  clearRadioSelection();
;

// Clears the form fields
const clearForm = () => 
  const form = document.querySelector('form');
  if (form) 
    (form.querySelector('input[name="title"]') as HTMLInputElement).value="";
    (form.querySelector('input[name="description"]') as HTMLInputElement).value="";
  
;

// Clears the radio button selection
const clearRadioSelection = () => 
  const radios = document.querySelectorAll('input[type="radio"]');
  radios.forEach(radio => (radio as HTMLInputElement).checked = false);
;

// Fetches the list of tasks from the server
const getTasks = async () => 
  try 
    const res = await axios.get(API_BASE_URL);
    setThings(res.data);
   catch (error) 
    console.error('Error fetching tasks:', error);
  
;

// Adds or edits a task based on the form data
const addEditTask = async (event: React.FormEvent) => 
  event.preventDefault();
  const form = event.target as HTMLFormElement;
  const formData = new FormData(form);
  const title = formData.get('title') as string;
  const description = formData.get('description') as string;

  try 
    if (selectedThing) 
      const res = await axios.put(`$API_BASE_URL/$selectedThing._id`,  title, description );
      setThings(things.map(thing => (thing._id === selectedThing._id ? res.data : thing)));
     else 
      const res = await axios.post(API_BASE_URL,  title, description );
      setThings([...things, res.data]);
    
    clearSelectedThing();
   catch (error) 
    console.error('Error adding/editing task:', error);
  
;

// Deletes the selected task
const deleteSelectedThing = async () => 
  if (selectedThing) 
    try 
      await axios.delete(`$API_BASE_URL/$selectedThing._id`);
      setThings(things.filter(thing => thing._id !== selectedThing._id));
      clearSelectedThing();
     catch (error) 
      console.error('Error deleting task:', error);
    
  
;
Enter fullscreen mode

Exit fullscreen mode

Finally, replace all the html being returned with the following code.

<div>
  <h1>Things</h1>

  <h2>All Tasks</h2>
  <ul>
    things.map((thing) => (
      <li key=thing._id>
        <input
          type="radio"
          id=thing._id
          name="thing"
          onClick=() => handleRadioClick(thing)
        />
        <label htmlFor=thing._id>
          thing.title - thing.description
        </label>
      </li>
    ))
  </ul>

  <h2>selectedThing === null ? 'Add' : 'Edit' Task</h2>
  <form onSubmit=addEditTask>
    Name: <input type="text" name="title" /><br />
    Description: <input type="text" name="description" /><br />
    <button type="submit">selectedThing === null ? 'Add Task' : 'Edit Task'</button>
  </form>

  <button onClick=clearSelectedThing>Clear Selected Thing</button>
  <button onClick=deleteSelectedThing>Delete Selected Thing</button>
</div>
Enter fullscreen mode

Exit fullscreen mode

Bam! There you go! You now have full CRUD applications.

full-stack MERN example

Check out our other content

Check out other tags:

Most Popular Articles