Let's Build a Simple Bulletin Board React App

Let's Build a Simple Bulletin Board React App

A beginner-friendly project to learn about drag-and-drop in React!

Featured on daily.dev

In an article I wrote on Making Draggable Components in React, a reader requested to share how I built the Bulletin Board App, a simple demo app to showcase draggable components in React.

demo

In this article, I'll be showing step-by-step how I made this simple app, perfect for React beginners to learn how to implement draggable components in their projects. Let's get started!

The App

This app can do the following:

  1. Allows user to type something in an input and press ENTER to generate a note of a random colour.
  2. Allows user to drag the note anywhere. Its position, colour and content will be saved even after the user exits the app.
  3. Allows the user to delete the note by clicking on the top-right 'X' button.

So let's build the app according to its 3 use cases.

Step 1: Install & import packages

Initialize a new Create React App by running the command:

npx create-react-app my-bulletin-board-app

Then, in the project's root folder, install the following packages that we need:

  1. react-draggable: to implement draggable features for the notes
  2. randomcolor: allow notes to be generated in random colours
  3. uuid: generates a unique identifier for each note

Install with the command:

npm install react-draggable randomcolor uuid

In App.js, import the packages and React useEffect and useState hooks.

import React, { useState, useEffect } from "react";
import "./App.css";
import Draggable from "react-draggable";
import { v4 as uuidv4 } from "uuid";
var randomColor = require("randomcolor");

Step 2: Initialize states

We need to create and initialize 2 states using useState hook.

  1. item: this is the value of the input field. Initialize as empty string.
  2. items: this is an array that contains all the notes generated, saved to localStorage. Initialize as empty array if localStorage has no saved items.
const [item, setItem] = useState("");
const [items, setItems] = useState(
    JSON.parse(localStorage.getItem("items")) || []
  );

Step 3: Create input element

We can create the HTML input and button elements in the return function of App.js as follows:

<input
    value={item}
    onChange={(e) => setItem(e.target.value)}
    placeholder="Enter something..."
    onKeyPress={(e) => keyPress(e)}
/>
<button onClick={newitem}>ENTER</button>

It looks something like:

image.png

<input> has the following attributes:

  • value: set to the value of the item state
  • onChange: update item state every time there is a change in the input's value
  • placeholder: description when there's nothing in the input field
  • onKeyPress: calls the keyPress function that checks if the key pressed is ENTER, call the newitem function.

The keyPress function is written like so:

const keyPress = (event) => {
    var code = event.keyCode || event.which;
    if (code === 13) {
      newitem();
    }
  };

This way, the user can type something in the input field then press ENTER key or click on the ENTER button to generate a new note on screen.

As for the ENTER button, when the user clicks on it, the newitem function will be called. Let's write this function next.

Step 4: newitem

This function generates a new note on the screen with the string the user typed in input (i.e. item). A note is an object with the following properties:

  • id: unique identifier generated using uuidv4()
  • item: the string content of the note, which is the state item's value
  • color: the background colour of the note, generated with randomColor({luminosity: "light",})
  • defaultPos: the x and y coordinates of the note. Initialized to {x:100, y:0}.
const newitem = () => {
    if (item.trim() !== "") {
     //if input is not blank, create a new item object
      const newitem = {
        id: uuidv4(),
        item: item,
        color: randomColor({luminosity: "light",}),
        defaultPos: { x: 100, y: 0 },
      };
      //add this new item object to the items array
      setItems((items) => [...items, newitem]);
      //reset item value to empty string
      setItem("");
    } else {
      alert("Enter a item");
      setItem("");
    }
};

Now, let's update our localStorage every time our items array is updated. We can use the useEffect hook to achieve this:

useEffect(() => {
    localStorage.setItem("items", JSON.stringify(items));
  }, [items]);

Now we should display our note objects from our items array on screen.

Step 5: Display items

In our return function, below our input and button element, we can display our notes using the map array method:

{items.map((item, index) => {
        return (
          <Draggable
            key={item.id}
            defaultPosition={item.defaultPos}
            onStop={(e, data) => {
              updatePos(data, index);
            }}
          >
            <div style={{ backgroundColor: item.color }} className="box">
              {`${item.item}`}
              <button id="delete" onClick={(e) => deleteNote(item.id)}>
                X
              </button>
            </div>
          </Draggable>
        );
      })}

For every note object in the items array, we will create a <Draggable> component in which:

  • key attribute =id of the object.
  • defaultPosition of the component = defaultPos of the object.
  • onStop, which is when the user stops dragging the note on screen, will call the updatePos function.

Inside the <Draggable> component, we have the <div> that will render the note's item property on a background with the note's colour property. And finally, we have an 'X' button which calls the deleteNote function upon clicked.

Now, we should be able to generate a new randomly-coloured note on screen every time we type something in the input and press ENTER.

gif1.gif

However, if we dragged the note and reload the page, the note's position will not be saved because we haven't written our updatePos function. Let's write that function next.

Step 6: updatePos

This function is called every time we stop dragging the note. That way, we can save the final position of the note to our items array in localStorage.

The next time we visit the page, the app will remember the last position of the note. It won't reset to {x:100, y:0}` all the time.

Here's how the function works:

  1. We pass data as the first parameter of the function. It contains the x and y coordinates of our note.
  2. Clone our items array into a new array called newArr.
  3. Get the index of the note in the array we want to update from the 2nd parameter index.
  4. Set the new coordinate values of the note in its defaultPos property.
  5. Set items to the value of the newArr.
const updatePos = (data, index) => {
    let newArr = [...items];
    newArr[index].defaultPos = { x: data.x, y: data.y };
    setItems(newArr);
 };

Great! Now the position of any note will be updated and saved to localStorage whenever it changes. Let's move on to the final function: deleteNote.

Step 7: deleteNote

In this function, the note will be deleted from both on screen and from the items array in localStorage.

This function is pretty straightforward. We can simply use the filter array method to remove the note whose id property matches the id parameter of the function.

const deleteNote = (id) => {
    setItems(items.filter((item) => item.id !== id));
 };

And that's it!

We should now have a simple working bulletin board app like the one below:

gif2.gif

Thanks for reading. I hope this is a helpful to implement React Draggable into your projects. For more details on React Draggable, feel free to check out my article Making Draggable Components in React.

Please like and share this article if it is helpful, and leave any questions in the comments below. Check out the demo or the repo of this app. For more information on the packages we used to build this app, feel free to read the section below.

Special thanks to Manas Garg for requesting this article. Sorry it took so long to finally publish. Thanks and cheers!


See Also

Did you find this article valuable?

Support Victoria Lo by becoming a sponsor. Any amount is appreciated!

ย