DEV Community

Jay Watson
Jay Watson

Posted on

Let'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

Things

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

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay