Next.js: Build an Interactive Simple Web-Based Code Snippet Manager

Written by

in

In the world of web development, managing code snippets efficiently can significantly boost your productivity. Imagine having a central repository where you can store, organize, and quickly access your frequently used code snippets. No more frantic searching through old projects or online forums! In this tutorial, we’ll build a simple, yet powerful, web-based code snippet manager using Next.js. This project will not only teach you the fundamentals of Next.js but also provide you with a practical tool that you can use every day.

Why Build a Code Snippet Manager?

As developers, we often find ourselves reusing code. Whether it’s a specific function, a CSS style, or a complex algorithm, the ability to quickly access and paste these snippets can save you a lot of time. A code snippet manager solves this problem by providing a centralized location for all your reusable code. This is particularly helpful when working on multiple projects or when collaborating with a team.

Here are some key benefits:

  • Increased Productivity: Quickly retrieve and use snippets, reducing the time spent rewriting or searching for code.
  • Code Reusability: Promote code reuse across projects.
  • Organization: Categorize and tag your snippets for easy retrieval.
  • Collaboration: Share snippets with team members.

What We’ll Build

Our code snippet manager will be a web application built with Next.js. It will allow users to:

  • Add new code snippets with a title, description, code, and optional tags.
  • View a list of all snippets.
  • Search and filter snippets by title, description, or tags.
  • Edit and delete existing snippets.
  • Store snippets locally (for simplicity).

This project will provide a solid foundation for understanding Next.js features, including:

  • Pages: Creating different routes and views.
  • Components: Building reusable UI elements.
  • State Management: Managing data within the application.
  • Styling: Applying styles to your components.
  • Event Handling: Responding to user interactions.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js and npm (or yarn): You’ll need Node.js (version 14 or higher) and npm (or yarn) installed on your machine. You can download them from nodejs.org.
  • A Code Editor: A code editor like Visual Studio Code, Sublime Text, or Atom.

Setting Up the Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app code-snippet-manager

This command will create a new Next.js project named “code-snippet-manager”. Navigate into the project directory:

cd code-snippet-manager

Now, start the development server:

npm run dev

Open your browser and go to http://localhost:3000. You should see the default Next.js welcome page. If everything is working, we can move on to building our application.

Project Structure Overview

Before we dive into the code, let’s take a quick look at the project structure that `create-next-app` generates for us. Understanding the structure will help you navigate the project and understand where to put your code.

Here’s a basic overview:

  • `pages/`: This directory contains your application’s pages. Each file in this directory represents a route in your application. For example, `pages/index.js` will be accessible at the root path (`/`).
  • `components/`: This is where you’ll store your reusable React components.
  • `styles/`: This directory is for your CSS or other styling files.
  • `public/`: Static assets like images and fonts go here.
  • `package.json`: This file contains information about your project, including its dependencies.

Creating the Snippet Form Component

First, let’s create a component for adding and editing snippets. Create a new file named `SnippetForm.js` inside the `components` directory. This component will handle the form for creating and updating snippets. Here’s the code:

// components/SnippetForm.js
import { useState } from 'react';

const SnippetForm = ({ initialSnippet, onSubmit }) => {
  const [title, setTitle] = useState(initialSnippet?.title || '');
  const [description, setDescription] = useState(initialSnippet?.description || '');
  const [code, setCode] = useState(initialSnippet?.code || '');
  const [tags, setTags] = useState(initialSnippet?.tags || '');

  const handleSubmit = (e) => {
    e.preventDefault();
    const snippetData = {
      title,
      description,
      code,
      tags: tags.split(',').map(tag => tag.trim()), // Convert tags to an array
    };
    onSubmit(snippetData);
    // Clear the form after submission
    setTitle('');
    setDescription('');
    setCode('');
    setTags('');
  };

  return (
    <form onSubmit={handleSubmit} className="mb-4">
      <div className="mb-3">
        <label htmlFor="title" className="block text-gray-700 text-sm font-bold mb-2">Title:</label>
        <input
          type="text"
          id="title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          required
        />
      </div>

      <div className="mb-3">
        <label htmlFor="description" className="block text-gray-700 text-sm font-bold mb-2">Description:</label>
        <textarea
          id="description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          rows="3"
        />
      </div>

      <div className="mb-3">
        <label htmlFor="code" className="block text-gray-700 text-sm font-bold mb-2">Code:</label>
        <textarea
          id="code"
          value={code}
          onChange={(e) => setCode(e.target.value)}
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          rows="10"
          required
        />
      </div>

      <div className="mb-3">
        <label htmlFor="tags" className="block text-gray-700 text-sm font-bold mb-2">Tags (comma separated):</label>
        <input
          type="text"
          id="tags"
          value={tags}
          onChange={(e) => setTags(e.target.value)}
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        />
      </div>

      <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
        Save Snippet
      </button>
    </form>
  );
};

export default SnippetForm;

This component uses the `useState` hook to manage the form input values. It takes an `initialSnippet` prop, which is used to pre-populate the form when editing an existing snippet. The `onSubmit` prop is a function that will be called when the form is submitted, passing the snippet data as an argument.

Explanation:

  • `useState` Hooks: We use `useState` hooks to manage the state of the form fields (title, description, code, and tags).
  • `handleSubmit` Function: This function is called when the form is submitted. It prevents the default form submission behavior, creates a `snippetData` object with the form values, and calls the `onSubmit` prop with the data. It also clears the form fields after submission.
  • Form Fields: The form includes input fields for the title and tags, and textareas for the description and code. We’ve added basic styling using inline classes (you can replace these with your preferred styling method).
  • `onSubmit` Prop: This prop will receive a function from the parent component that handles the actual saving of the snippet.

Creating the Snippet List Component

Next, let’s create a component to display the list of code snippets. Create a file named `SnippetList.js` inside the `components` directory. This component will render a list of snippets, including their title, description, and tags. It will also include buttons for editing and deleting snippets. Here’s the code:

// components/SnippetList.js
import Link from 'next/link';

const SnippetList = ({ snippets, onDelete, onEdit }) => {
  return (
    <div>
      <h2 className="text-2xl font-bold mb-4">Code Snippets</h2>
      <ul>
        {snippets.map((snippet) => (
          <li key={snippet.id} className="border rounded p-4 mb-4 shadow-md">
            <h3 className="text-lg font-semibold mb-2">{snippet.title}</h3>
            <p className="text-gray-700 mb-2">{snippet.description}</p>
            <p className="text-sm text-gray-500">Tags: {snippet.tags.join(', ')}</p>
            <div className="mt-2">
              <button
                onClick={() => onEdit(snippet)}
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded mr-2"
              >
                Edit
              </button>
              <button
                onClick={() => onDelete(snippet.id)}
                className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded"
              >
                Delete
              </button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default SnippetList;

This component receives a `snippets` prop, which is an array of snippet objects. It maps over the snippets array and renders a list item for each snippet, displaying its title, description, and tags. It also includes “Edit” and “Delete” buttons for each snippet. The `onDelete` and `onEdit` props are functions that will be called when the corresponding buttons are clicked. We are using Next.js Link component for future enhancements, like linking to a detailed view of a snippet.

Explanation:

  • `snippets` Prop: This prop is an array of snippet objects.
  • Mapping Snippets: The `map` function iterates over the `snippets` array and renders a `<li>` element for each snippet.
  • Snippet Details: The title, description, and tags are displayed for each snippet.
  • Edit and Delete Buttons: Each snippet has “Edit” and “Delete” buttons. When clicked, these buttons call the `onEdit` and `onDelete` functions, respectively, passing the snippet’s ID or the full snippet object.

Building the Main Page (index.js)

Now, let’s create the main page of our application, which will display the snippet form and the snippet list. Open `pages/index.js` and replace its contents with the following code:

// pages/index.js
import { useState, useEffect } from 'react';
import SnippetForm from '../components/SnippetForm';
import SnippetList from '../components/SnippetList';

const Home = () => {
  const [snippets, setSnippets] = useState([]);
  const [editingSnippet, setEditingSnippet] = useState(null);

  useEffect(() => {
    // Load snippets from local storage on component mount
    const storedSnippets = localStorage.getItem('snippets');
    if (storedSnippets) {
      setSnippets(JSON.parse(storedSnippets));
    }
  }, []);

  useEffect(() => {
    // Save snippets to local storage whenever the snippets state changes
    localStorage.setItem('snippets', JSON.stringify(snippets));
  }, [snippets]);

  const handleAddSnippet = (newSnippet) => {
    const newSnippetWithId = { ...newSnippet, id: Date.now() };
    setSnippets([...snippets, newSnippetWithId]);
  };

  const handleEditSnippet = (snippetToEdit) => {
    setEditingSnippet(snippetToEdit);
  };

  const handleUpdateSnippet = (updatedSnippet) => {
    setSnippets(
      snippets.map((snippet) =>
        snippet.id === updatedSnippet.id ? { ...updatedSnippet } : snippet
      )
    );
    setEditingSnippet(null);
  };

  const handleDeleteSnippet = (snippetId) => {
    setSnippets(snippets.filter((snippet) => snippet.id !== snippetId));
  };

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">Code Snippet Manager</h1>

      <SnippetForm
        initialSnippet={editingSnippet}
        onSubmit={editingSnippet ? handleUpdateSnippet : handleAddSnippet}
      />

      <SnippetList
        snippets={snippets}
        onDelete={handleDeleteSnippet}
        onEdit={handleEditSnippet}
      />
    </div>
  );
};

export default Home;

This is the main component of our application. It manages the state of the snippets, handles adding, editing, and deleting snippets, and renders the `SnippetForm` and `SnippetList` components. It also uses local storage to persist the snippets between sessions.

Explanation:

  • State Management: The `snippets` state variable holds an array of snippet objects. The `editingSnippet` state variable holds the snippet currently being edited.
  • `useEffect` Hooks:
    • The first `useEffect` hook loads snippets from local storage when the component mounts.
    • The second `useEffect` hook saves snippets to local storage whenever the `snippets` state changes.
  • `handleAddSnippet` Function: This function is called when a new snippet is submitted. It adds the new snippet to the `snippets` array.
  • `handleEditSnippet` Function: This function is called when the “Edit” button is clicked. It sets the `editingSnippet` state to the selected snippet.
  • `handleUpdateSnippet` Function: This function is called when the edited snippet is submitted. It updates the corresponding snippet in the `snippets` array.
  • `handleDeleteSnippet` Function: This function is called when the “Delete” button is clicked. It removes the selected snippet from the `snippets` array.
  • Rendering Components: The page renders the `SnippetForm` component (passing the `editingSnippet` to pre-populate the form and the appropriate `onSubmit` handler) and the `SnippetList` component (passing the `snippets` array, `onDelete` handler, and `onEdit` handler).

Styling the Application (Optional)

For styling, you can use any CSS framework or write your own CSS. For simplicity, we’ll use inline styles in the components. However, for a production application, it’s recommended to use a CSS framework like Tailwind CSS, Bootstrap, or Material UI, or to create a separate CSS file for better organization and maintainability.

If you choose to use Tailwind CSS (recommended for ease of use), you can install it using:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Then, configure Tailwind in `tailwind.config.js` and add the directives to `styles/globals.css`. After installing Tailwind, you can use its utility classes directly in your JSX to style your components. For example, adding the `className` attributes in the code above applies basic styling.

Running and Testing the Application

Now that we’ve built the application, let’s run it and test it to make sure everything is working as expected. Make sure your development server is running (`npm run dev`) and go to http://localhost:3000 in your browser.

Testing Steps:

  • Adding a Snippet: Fill out the form fields (title, description, code, and tags) and click the “Save Snippet” button. The new snippet should appear in the list below the form.
  • Editing a Snippet: Click the “Edit” button on an existing snippet. The form should pre-populate with the snippet’s data. Modify the fields and click “Save Snippet”. The snippet in the list should update.
  • Deleting a Snippet: Click the “Delete” button on a snippet. The snippet should be removed from the list.
  • Persistence: Refresh the page. The snippets you added should still be there, thanks to local storage.

If all the tests pass, congratulations! You’ve successfully built a simple code snippet manager using Next.js.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Incorrect Imports: Make sure you’re importing components and hooks correctly. Double-check the file paths in your `import` statements.
  • State Not Updating: If the UI doesn’t update after adding, editing, or deleting a snippet, check your state updates. Ensure you’re using the correct methods to update the state (e.g., using `setSnippets` correctly).
  • Local Storage Issues: If snippets aren’t being saved to local storage, verify that you’re correctly using `localStorage.setItem` and `localStorage.getItem` and that you’re stringifying and parsing the data correctly with `JSON.stringify` and `JSON.parse`. Also, ensure that your browser allows local storage.
  • Styling Problems: If your styles aren’t being applied, double-check your CSS file paths and ensure your CSS framework is correctly configured. If using inline styles, check for typos.
  • Form Submission Errors: Ensure your form elements have the correct `name` attributes and that your form’s `onSubmit` handler is correctly wired up. Also, check that you’ve handled the `e.preventDefault()` to prevent the default form submission behavior.

Enhancements and Future Improvements

This is a basic implementation of a code snippet manager, and there’s a lot of room for improvement. Here are some ideas for future enhancements:

  • Code Highlighting: Implement code highlighting using a library like Prism.js or highlight.js to make the code snippets more readable.
  • Syntax Highlighting: Add syntax highlighting to the code snippets to improve readability.
  • Search and Filtering: Implement advanced search and filtering options, such as filtering by language or date.
  • Tags Autocomplete: Add an autocomplete feature for tags to make it easier to enter tags.
  • User Authentication: Add user authentication to allow multiple users to save and manage snippets.
  • Database Integration: Instead of using local storage, integrate a database (like MongoDB, PostgreSQL, or Firebase) to store the snippets.
  • Import/Export Functionality: Add the ability to import and export snippets, allowing users to back up their snippets or share them with others.
  • Code Formatting: Integrate a code formatter (like Prettier) to automatically format the code snippets.
  • Dark Mode: Add a dark mode toggle to improve the user experience.
  • Mobile Responsiveness: Make the application responsive for different screen sizes.

Key Takeaways

In this tutorial, we’ve built a functional code snippet manager using Next.js. We’ve covered the basics of Next.js, including creating pages, components, handling state, and using local storage. This project provides a solid foundation for building more complex web applications with Next.js. Remember to break down the project into smaller, manageable parts. Test your code frequently and don’t be afraid to experiment. By building this project, you’ve gained practical experience with Next.js and learned how to build a useful tool that can improve your development workflow.

Building this code snippet manager has given you a practical understanding of how to manage state, handle user input, and persist data in a Next.js application. You’ve learned how to create reusable components, structure your project, and implement basic CRUD (Create, Read, Update, Delete) operations. This project serves as a stepping stone to building more advanced Next.js applications, so keep practicing and exploring new features. Your journey as a Next.js developer has just begun, and with each project, you will improve your skills and efficiency. The ability to quickly store, retrieve, and organize code snippets will undoubtedly streamline your development process and boost your overall productivity. So, go forth, and code with greater efficiency!