Annotated MERN III: Frontend

3 October 2021

This post continues our look at James Vickery’s simple-mern repository. Today we’ll look at the React frontend, under the client/ folder.

package.json

We’ll ignore package-lock.json, since it was covered previously.

package.json looks very similar to what create-react-app spits out. There are some new options, though, so let’s look at them.

  {
    "proxy": "http://localhost:5000/",
    "eslintConfig": {
      "extends": "react-app"
    },
    "browserslist": [
      ">0.2%",
      "not dead",
      "not ie <= 11",
      "not op_mini all"
    ]
  }

These are all development-only options, in a sense:

Let’s also look at the dependencies and scripts:

{
  "dependencies": {
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-scripts": "2.1.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

The only dependencies we have right now are

In fact, create-react-app is hiding quite a lot of dependencies from us, and running npm run eject will show them. It’ll also reveal the Webpack configuration, some Jest configuration, and a host of other things. For now we’ll leave all this in the box, though.

src/

App.css, index.css

This is just CSS. I’ll ignore it for now. index.css in particular is autogenerated by create-react-app.

index.js

This is the first file compiled, and all it does is tell react-dom to render the App component, so there’s not much to see here.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

App.js

Here we get the real meat of the application. First, the imports.

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

import TasksList from './components/TaskList';

The remainder of the application is a React component, written as a function. The more traditional style is to write this as a class that extends React.Component.

(Talk some about how these styles differ – hooks vs state.)

const App = () => {
...
};

Within this component, the first thing we encounter is some app-level state. This is implemented through React’s useState hook, which returns a getter and setter for a state field. We just pass in the field’s initial value.

const [tasks, setTasks] = useState([]);
const [newTaskTitle, setNewTaskTitle] = useState('');

Next we encounter a React effect hook. This specifies something that will happen when the component loads. The name effect is intended to evoke side effect.

const getTasks = useCallback(() => {
  fetch('/api/tasks')
    .then(res => res.json())
    .then(setTasks);
});

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

The effect getTasks runs an API call to pull the task list from Mongo. We saw the meat of this API call in the backend post. Then it sets the task list inside the app.

The next thing we come to is an event handler.

const clickAddTask = event => {
  event.preventDefault();

  fetch('/api/tasks/add', {
    method: 'post',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: newTaskTitle }),
  }).then(() => {
    setNewTaskTitle('');
    getTasks();
  });
};

As we’ll see below, and as the name implies, this fires upon clicking the “Add Task” button.

Ordinarily, clicking on the submit button would reload the page; we call event.preventDefault() to stop this from happening.

Then we send an API call to the server to actually add the task to Mongo. Having done that, we reset the value of the title box (setNewTaskTitle('')) and update the task list (getTasks().)

Finally, we have the component’s actual HTML structure. This would appear under the render() method in a component class.

  return (
    <div className="App">
      <h1>My Tasks</h1>

      <TasksList tasks={tasks} updateTasks={getTasks} />

      <form onSubmit={clickAddTask}>
        <input
          type="text"
          size="30"
          placeholder="New Task"
          value={newTaskTitle}
          onChange={event => setNewTaskTitle(event.target.value)}
        />
        <input className="btn-primary" type="submit" value="Add" />
      </form>
    </div>
  );

The form structure is nothing new, although some keywords (className, for one) have been changed to avoid collision with JavaScript reserved words.

The interesting part is the TasksList line:

  <TasksList tasks={tasks} updateTasks={getTasks} />

This renders a TaskList component with two properties. We’ll see in a moment where these properties are used.

components/TaskList.js

This file specifies the structure of the TaskList component. There are two component methods and the HTML structure to look at, but first let’s examine the signature.

const TaskList = ({ tasks, updateTasks }) => [...]

If this component were a class, its constructor would take an optional props argument containing component-level properties. These properties, tasks and updateTasks, are what we just saw above.

updateTasks is a function, so thinking of it as a property may be strange. It’s set up this way because updateTasks is defined along with tasks at the application level. It may be a poor architectural decision, but for an app of this size it’s probably not worth worrying about.

Let’s look at the component methods.

  const clickDeleteTask = (event, task) => {
    event.preventDefault();

    fetch(`/api/tasks/delete/${task._id}`, {
      method: 'delete',
    })
      .then(res => res.json())
      .then(() => updateTasks());
  };

This is an event handler. Nothing new here; this simply stops anything that would normally happen when you click on the trigger, runs an API call to delete the passed task instead, and updates the task list.

  const toggleDone = task => {
    fetch(`/api/tasks/update/${task._id}`, {
      method: 'post',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ done: !task.done }),
    }).then(() => updateTasks());
  };

More of the same here. API call to update the task, then update the task list.

Now let’s check out the HTML structure.

return (
  <ul className="tasks">
    {tasks.map(task => (
      <li key={task._id}>
        <label className={task.done ? 'done' : ''}>
          <input
            type="checkbox"
            checked={task.done}
            onChange={() => toggleDone(task)}
          />{' '}
          {task.title}
          <svg
            onClick={event => clickDeleteTask(event, task)}
            className="delete-button"
            [...]
          >
          [...]
          </svg>
        </label>
      </li>
    ))}
  </ul>
);

This simply renders a <li> for each task containing a toggle box, a title, and an SVG (whose fiddly details I have omitted) that functions as the delete button. (It renders to an X.)

There’s only one new feature to discuss:

<li key={task._id}>

A React key is just what it sounds like: a unique identifier. (For this task item, we just use the unique ID that Mongo assigns it.) Without them, we’d have to rerender the entire component every time one of the tasks changed. With them, React will search for the key and rerender only the item that matches.

src/public/

This contains some files autogenerated by create-react-app. index.js plugs in here. Otherwise there’s not much to say.