Build a Simple React JavaScript Interactive Code Snippet Manager: A Beginner’s Guide

Ever found yourself repeatedly typing the same code snippets? Maybe you have a collection of useful functions, CSS styles, or HTML structures that you use over and over. Wouldn’t it be great to have a central place to store and easily retrieve these snippets, so you can quickly paste them into your projects? That’s where a code snippet manager comes in handy. This tutorial will guide you through building a simple, interactive code snippet manager using ReactJS. We’ll cover everything from setting up your project to adding features like adding, editing, and deleting snippets. By the end of this guide, you’ll have a functional snippet manager and a solid understanding of fundamental React concepts.

Why Build a Code Snippet Manager?

As developers, we often reuse code. A code snippet manager streamlines this process. Here’s why it’s a valuable tool:

  • Increased Productivity: Quickly access and paste frequently used code, saving time and effort.
  • Reduced Errors: Avoid typos and errors by reusing tested code snippets.
  • Improved Code Consistency: Ensure consistent coding style across projects.
  • Learning Tool: A great way to learn and practice React concepts.

What We’ll Build

Our React code snippet manager will have the following features:

  • Add Snippets: A form to input the snippet’s name, description, and code.
  • View Snippets: A list of all saved snippets.
  • Edit Snippets: The ability to modify existing snippets.
  • Delete Snippets: The option to remove snippets.
  • Code Highlighting: Basic code highlighting to make snippets easier to read.

Prerequisites

Before we start, make sure you have the following:

  • Node.js and npm (or yarn) installed: This is essential for managing JavaScript packages and running our React application. You can download them from nodejs.org.
  • A Code Editor: Visual Studio Code, Sublime Text, or any other code editor of your choice.
  • Basic Knowledge of HTML, CSS, and JavaScript: Familiarity with these languages will help you understand the code.
  • A basic understanding of React: You should know the basics of components, props, and state.

Project Setup

Let’s get started by creating our React project. Open your terminal and run the following command:

npx create-react-app code-snippet-manager

This command creates a new React app named “code-snippet-manager”. Navigate into the project directory:

cd code-snippet-manager

Now, let’s clean up the boilerplate code. Open the `src` directory in your code editor and delete the following files:

  • App.css
  • App.test.js
  • logo.svg
  • reportWebVitals.js
  • setupTests.js

Modify `src/App.js` to look like this:

import React, { useState } from 'react';
import './App.css';

function App() {
  const [snippets, setSnippets] = useState([]);

  return (
    <div className="App">
      <header className="App-header">
        <h1>Code Snippet Manager</h1>
      </header>
      <main>
        {/*  Snippet Form Component will go here */}
        {/*  Snippet List Component will go here */}
      </main>
    </div>
  );
}

export default App;

Create a new file named `src/App.css` and add some basic styling:

.App {
  font-family: sans-serif;
  text-align: center;
}

.App-header {
  background-color: #282c34;
  color: white;
  padding: 20px;
}

main {
  padding: 20px;
}

.snippet-form {
  margin-bottom: 20px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.snippet-form input[type="text"],
.snippet-form textarea {
  width: 100%;
  padding: 8px;
  margin-bottom: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box; /* Important for width to include padding */
}

.snippet-form button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.snippet-form button:hover {
  background-color: #3e8e41;
}

.snippet-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.snippet-card {
  width: 300px;
  margin: 10px;
  padding: 15px;
  border: 1px solid #ddd;
  border-radius: 8px;
  text-align: left;
  background-color: #f9f9f9;
}

.snippet-card h3 {
  margin-top: 0;
  margin-bottom: 10px;
}

.snippet-card pre {
  background-color: #eee;
  padding: 10px;
  border-radius: 4px;
  overflow-x: auto; /* For horizontal scrolling of code */
}

.snippet-card code {
  font-family: monospace;
}

.snippet-card button {
  margin-top: 10px;
  padding: 8px 12px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.snippet-card .edit-button {
  background-color: #008CBA;
  color: white;
  margin-right: 5px;
}

.snippet-card .delete-button {
  background-color: #f44336;
  color: white;
}

.snippet-card button:hover {
  opacity: 0.8;
}

Finally, run the application using:

npm start

This will start the development server, and you should see a heading “Code Snippet Manager” in your browser. Now we have our basic project setup, let’s start building the components.

Building the Snippet Form Component

The Snippet Form component will allow users to add new snippets. Create a new file named `src/SnippetForm.js` and add the following code:

import React, { useState } from 'react';

function SnippetForm({ onAddSnippet }) {
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [code, setCode] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!name.trim() || !code.trim()) {
      alert('Please fill in the name and code fields.');
      return;
    }
    const newSnippet = {
      name,
      description,
      code,
    };
    onAddSnippet(newSnippet);
    setName('');
    setDescription('');
    setCode('');
  };

  return (
    <form className="snippet-form" onSubmit={handleSubmit}>
      <h3>Add New Snippet</h3>
      <label htmlFor="name">Name:</label>
      <input
        type="text"
        id="name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <label htmlFor="description">Description:</label>
      <input
        type="text"
        id="description"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
      />

      <label htmlFor="code">Code:</label>
      <textarea
        id="code"
        value={code}
        onChange={(e) => setCode(e.target.value)}
        rows="5"
      />

      <button type="submit">Add Snippet</button>
    </form>
  );
}

export default SnippetForm;

Let’s break down this component:

  • State Variables: We use `useState` hooks to manage the input fields: `name`, `description`, and `code`.
  • `handleSubmit` Function: This function is called when the form is submitted. It prevents the default form submission behavior, creates a new snippet object, calls the `onAddSnippet` function (which we’ll define in `App.js`), and resets the input fields. It also includes basic validation to prevent empty submissions.
  • JSX Structure: The form includes input fields for the snippet’s name, description, and code, along with labels. The `onChange` event handlers update the state variables as the user types.
  • `onAddSnippet` prop: This prop is a function passed down from the parent component (`App.js`). It will handle adding the new snippet to the list of snippets.

Now, let’s integrate this component into `App.js`. Import the `SnippetForm` component and modify the `App` component as follows:

import React, { useState } from 'react';
import './App.css';
import SnippetForm from './SnippetForm';
import SnippetList from './SnippetList'; // Import the SnippetList component

function App() {
  const [snippets, setSnippets] = useState([]);

  const handleAddSnippet = (newSnippet) => {
    setSnippets([...snippets, newSnippet]);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Code Snippet Manager</h1>
      </header>
      <main>
        <SnippetForm onAddSnippet={handleAddSnippet} />
        <SnippetList snippets={snippets}  />
      </main>
    </div>
  );
}

export default App;

Here, we import the `SnippetForm` component. The `handleAddSnippet` function is defined to update the `snippets` state by adding the new snippet to the existing array. We pass this function as a prop (`onAddSnippet`) to the `SnippetForm` component. We also pass the `snippets` to the `SnippetList` component, which we will create next.

Building the Snippet List Component

The Snippet List component will display the list of snippets. Create a new file named `src/SnippetList.js` and add the following code:

import React from 'react';

function SnippetList({ snippets }) {
  return (
    <div className="snippet-list">
      {snippets.map((snippet, index) => (
        <div className="snippet-card" key={index}>
          <h3>{snippet.name}</h3>
          <p>{snippet.description}</p>
          <pre><code>{snippet.code}</code></pre>
        </div>
      ))}
    </div>
  );
}

export default SnippetList;

Let’s break down this component:

  • `snippets` prop: This prop receives the array of snippets from the parent component (`App.js`).
  • `map()` method: The `map()` method iterates through the `snippets` array and renders a `div` element (a “snippet card”) for each snippet.
  • Snippet Card: Each card displays the snippet’s name, description, and code. The code is wrapped in a `<pre>` and `<code>` tag for basic formatting.
  • `key` prop: The `key` prop is essential for React to efficiently update the list. It should be a unique identifier for each item. In this case, we’re using the index, but in a real-world application, you’d likely use a unique ID for each snippet.

Now, let’s test our application. Run `npm start` in your terminal. You should be able to add snippets using the form, and they should appear in the list below. You can add some sample snippets to verify it’s working.

Adding Edit and Delete Functionality

Let’s add the ability to edit and delete snippets. We’ll modify the `SnippetList` component to include edit and delete buttons. First, modify the `SnippetList.js` file to include the edit and delete buttons:

import React from 'react';

function SnippetList({ snippets, onDeleteSnippet, onEditSnippet }) {
  return (
    <div className="snippet-list">
      {snippets.map((snippet, index) => (
        <div className="snippet-card" key={index}>
          <h3>{snippet.name}</h3>
          <p>{snippet.description}</p>
          <pre><code>{snippet.code}</code></pre>
          <button className="edit-button" onClick={() => onEditSnippet(index)}>Edit</button>
          <button className="delete-button" onClick={() => onDeleteSnippet(index)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

export default SnippetList;

Here we’ve added two buttons: “Edit” and “Delete”. We also added two new props, `onDeleteSnippet` and `onEditSnippet`, which will be functions passed down from the `App` component. We also pass the index of the snippet when the edit or delete button is clicked. Now, we’ll modify `App.js` to handle the edit and delete actions. Update `App.js`:

import React, { useState } from 'react';
import './App.css';
import SnippetForm from './SnippetForm';
import SnippetList from './SnippetList';

function App() {
  const [snippets, setSnippets] = useState([]);
  const [editingIndex, setEditingIndex] = useState(-1); // To track which snippet is being edited
  const [editFormValues, setEditFormValues] = useState({});

  const handleAddSnippet = (newSnippet) => {
    setSnippets([...snippets, newSnippet]);
  };

  const handleDeleteSnippet = (index) => {
    const newSnippets = [...snippets];
    newSnippets.splice(index, 1);
    setSnippets(newSnippets);
  };

  const handleEditSnippet = (index) => {
    setEditingIndex(index);
    setEditFormValues(snippets[index]);
  };

  const handleUpdateSnippet = (index, updatedSnippet) => {
    const newSnippets = [...snippets];
    newSnippets[index] = updatedSnippet;
    setSnippets(newSnippets);
    setEditingIndex(-1);
    setEditFormValues({});
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Code Snippet Manager</h1>
      </header>
      <main>
        {editingIndex === -1 ? (
          <SnippetForm onAddSnippet={handleAddSnippet} />
        ) : (
          <EditSnippetForm
            snippet={editFormValues}
            onUpdateSnippet={(updatedSnippet) => handleUpdateSnippet(editingIndex, updatedSnippet)}
            onCancel={() => {
              setEditingIndex(-1);
              setEditFormValues({});
            }}
          />
        )}
        <SnippetList
          snippets={snippets}
          onDeleteSnippet={handleDeleteSnippet}
          onEditSnippet={handleEditSnippet}
        />
      </main>
    </div>
  );
}

export default App;

Here’s what’s changed:

  • `editingIndex` state: We added `editingIndex` to track which snippet is currently being edited. It starts at -1 (meaning no snippet is being edited). We also add a `editFormValues` state to store the values of the snippet currently being edited.
  • `handleDeleteSnippet` function: This function removes a snippet from the `snippets` array using the `splice()` method.
  • `handleEditSnippet` function: This function sets the `editingIndex` to the index of the snippet to be edited and sets the edit form values.
  • `handleUpdateSnippet` function: This function updates the snippet in the `snippets` array.
  • Conditional Rendering of SnippetForm: We use a ternary operator to conditionally render either the `SnippetForm` (for adding new snippets) or an `EditSnippetForm` component (which we’ll create next) when `editingIndex` is not -1.
  • Passing Props: We pass the `handleDeleteSnippet` and `handleEditSnippet` functions to the `SnippetList` component.

Now, let’s create the `EditSnippetForm` component. Create a new file named `src/EditSnippetForm.js`:

import React, { useState, useEffect } from 'react';

function EditSnippetForm({ snippet, onUpdateSnippet, onCancel }) {
  const [name, setName] = useState(snippet.name || '');
  const [description, setDescription] = useState(snippet.description || '');
  const [code, setCode] = useState(snippet.code || '');

  useEffect(() => {
    setName(snippet.name || '');
    setDescription(snippet.description || '');
    setCode(snippet.code || '');
  }, [snippet]);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!name.trim() || !code.trim()) {
      alert('Please fill in the name and code fields.');
      return;
    }
    const updatedSnippet = {
      name,
      description,
      code,
    };
    onUpdateSnippet(updatedSnippet);
  };

  return (
    <form className="snippet-form" onSubmit={handleSubmit}>
      <h3>Edit Snippet</h3>
      <label htmlFor="name">Name:</label>
      <input
        type="text"
        id="name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <label htmlFor="description">Description:</label>
      <input
        type="text"
        id="description"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
      />

      <label htmlFor="code">Code:</label>
      <textarea
        id="code"
        value={code}
        onChange={(e) => setCode(e.target.value)}
        rows="5"
      />
      <button type="submit">Update Snippet</button>
      <button type="button" onClick={onCancel}>Cancel</button>
    </form>
  );
}

export default EditSnippetForm;

Let’s break down this component:

  • `snippet` prop: This prop receives the snippet object that’s being edited, which we passed in App.js.
  • State Variables: We use `useState` hooks to manage the input fields, initialized with the existing snippet’s values.
  • `useEffect` Hook: This hook ensures the form values are updated when the `snippet` prop changes (i.e., when a different snippet is selected for editing).
  • `handleSubmit` Function: This function is called when the form is submitted. It prevents the default form submission behavior, creates a new snippet object, calls the `onUpdateSnippet` function (which we’ll define in `App.js`), and resets the input fields.
  • `onUpdateSnippet` prop: This prop is a function passed down from the parent component (`App.js`). It will handle updating the snippet in the list of snippets.
  • `onCancel` prop: This prop is a function passed down from the parent component (`App.js`). This is used to cancel the editing.
  • JSX Structure: The form includes input fields for the snippet’s name, description, and code, along with labels. The `onChange` event handlers update the state variables as the user types.

With these changes, you should now be able to add, edit, and delete snippets in your React code snippet manager. Test it thoroughly to ensure all features work as expected.

Adding Code Highlighting

To make the code snippets more readable, let’s add code highlighting. We’ll use a library called `react-syntax-highlighter`. First, install the library in your project:

npm install react-syntax-highlighter

Then, import the necessary components and use them in the `SnippetList.js` file. Update the `src/SnippetList.js` file:

import React from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism';

function SnippetList({ snippets, onDeleteSnippet, onEditSnippet }) {
  return (
    <div className="snippet-list">
      {snippets.map((snippet, index) => (
        <div className="snippet-card" key={index}>
          <h3>{snippet.name}</h3>
          <p>{snippet.description}</p>
          <SyntaxHighlighter language="javascript" style={dark}>
            {snippet.code}
          </SyntaxHighlighter>
          <button className="edit-button" onClick={() => onEditSnippet(index)}>Edit</button>
          <button className="delete-button" onClick={() => onDeleteSnippet(index)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

export default SnippetList;

Here’s what’s new:

  • Import Statements: We import `SyntaxHighlighter` from `react-syntax-highlighter` and the `dark` theme. You can choose different themes.
  • `SyntaxHighlighter` Component: We wrap the `snippet.code` within the `SyntaxHighlighter` component, specifying the `language` (e.g., “javascript”) and the `style` (theme). You can adjust the language based on the snippet’s content.

Now, the code snippets should be syntax-highlighted, making them much easier to read. You might need to adjust the `language` prop to match the code inside the snippet (e.g., “html”, “css”, “python”, etc.).

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Incorrect Import Paths: Double-check your import statements. Make sure the file paths are correct. This is a common source of errors.
  • Missing `key` Prop: When rendering lists, always provide a unique `key` prop for each element. If you don’t, React will throw a warning and may not update the list correctly.
  • State Not Updating: If the UI isn’t updating after a state change, make sure you’re using the correct state update function (e.g., `setSnippets`) and that you’re not directly mutating the state (e.g., don’t do `snippets.push()`, use `setSnippets([…snippets, newSnippet])` instead).
  • Incorrect Event Handling: Ensure your event handlers are correctly bound (e.g., `onClick={() => handleDeleteSnippet(index)}`).
  • Typographical Errors: Carefully check your code for typos, especially in variable names and component names.
  • CSS Issues: If the styling isn’t working as expected, check your CSS files and ensure that the class names are correct and the CSS rules are being applied. Use your browser’s developer tools to inspect the elements and see if there are any CSS conflicts or errors.
  • Console Errors: Always check the browser’s console for any error messages. They often provide valuable clues about what’s going wrong.

Key Takeaways

  • Component-Based Architecture: React applications are built using components. Each component is responsible for a specific part of the UI.
  • State Management: Use the `useState` hook to manage component state. When state changes, React re-renders the component.
  • Props: Use props to pass data from parent components to child components.
  • Event Handling: Use event handlers (e.g., `onClick`, `onChange`) to respond to user interactions.
  • Conditional Rendering: Use conditional rendering (e.g., ternary operators) to display different content based on the application’s state.
  • Reusability: Components should be reusable. Build components that can be used in multiple places within your application.

FAQ

  1. Can I store the snippets in local storage? Yes, you can. You can use the `useEffect` hook with an empty dependency array to load the snippets from local storage when the component mounts and save the snippets to local storage whenever the snippets state changes. This will persist the snippets even when the user closes the browser.
  2. How can I add different languages for the code highlighting? The `react-syntax-highlighter` library supports many languages. You just need to set the `language` prop of the `SyntaxHighlighter` component to the appropriate language (e.g., “javascript”, “python”, “html”, “css”).
  3. How can I deploy this app? You can deploy your React app to platforms like Netlify, Vercel, or GitHub Pages. These platforms provide free hosting and make it easy to deploy your app. You’ll typically build your app using `npm run build` and then deploy the contents of the `build` directory.
  4. How can I improve the UI? You can improve the UI using CSS frameworks like Bootstrap, Material-UI, or Tailwind CSS. These frameworks provide pre-built components and styling, making it easier to create a visually appealing application.
  5. How can I add search functionality? You can add a search input field and filter the snippets array based on the search term. Create a new state variable for the search term and use the `filter` method on the snippets array.

Building a code snippet manager is a valuable learning experience. It reinforces the fundamentals of React and provides a practical application of the concepts. By following this guide, you’ve gained hands-on experience with components, state management, event handling, and conditional rendering. You’ve also learned how to integrate third-party libraries for code highlighting. This project can be expanded upon. You could add features like tags, categorization, and the ability to export snippets. With the knowledge you’ve gained, you can now confidently tackle more complex React projects.