learn react ui logoLearnReactUI
CRUD Mocking With MSW

CRUD Operations With MSW Example

This project showcases CRUD operations mocking using MSW (Mock Service Worker) to simulate API responses. It facilitates seamless testing and development by providing mocked data interactions, handling user inputs, and simulating dynamic API behavior for a comprehensive testing environment.

[Demo] [Video]

1. Use Case

Our objective in this example is to create a basic Task Management application using CRUD capabilities. While implementing these improvements:

  • Network layer (fetch API)
  • Mock Layer (Server Worker)
  • Data Layer (Mock Server Worker Data Rest) Task Application

We'll have learnt. The websites below provide information on using this program.

The application actually consists of 2 basic elements. TaskApp and its corresponding TaskHandler

2. Development of the Task Application

In the TaskApp application, we first define the State definitions. The state that holds all the tasks, a state where you will enter a new task and finally the update time.

const [tasks, setTasks] = React.useState(null);
const [currentTitle, setCurrentTitle] = React.useState("");
const [lastUpdatedAt, setLastUpdatedAt] = React.useState(null);

Fetching all Task

const fetchTasks = () => {
  fetch(`${restApiUri}/tasks`)
    .then((res) => res.json())
    .then(setTasks)
    .then(() => setLastUpdatedAt(Date.now()));
};

We ensure that this is fetched 1 time when loading the first page

React.useEffect(() => {
  fetchTasks();
}, []);

Create a new Task function

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

  fetch(`${restApiUri}/tasks`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: currentTitle,
    }),
  }).then(() => {
    setCurrentTitle("");
    fetchTasks();
  });
};

Update Existing Task

const updateTask = (id, nextTask) => {
  fetch(`${restApiUri}/tasks/${id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(nextTask),
  }).then(fetchTasks);
};

Delete Existing Task

const deleteTask = (id) => {
  fetch(`${restApiUri}/tasks/${id}`, { method: "DELETE" }).then(fetchTasks);
};

The UI I will talk about below consists of 3 parts;

Input Field where you can enter a new Task

<header className={`relative ${hasTasks ? "shadow-sm" : ""}`}>
  <form onSubmit={handleCreateTask}>
    <Input
      name="title"
      value={currentTitle}
      onChange={({ target }) => setCurrentTitle(target.value)}
      autoComplete="off"
      placeholder="Enter Task Name"
    />
  </form>
</header>

Task List

<ul>
  {tasks.map((task) => (
    <li
      key={task.id}
      style={{
        display: "flex",
        justifyContent: "space-between",
        padding: "0.5rem 1rem",
      }}
    >
      <span>
        <Checkbox
          checked={task.isDone}
          onChange={({ target }) => {
            updateTask(task.id, {
              title: task.title,
              isDone: target.checked,
            });
          }}
        />
        <span
          style={{ cursor: "pointer", marginLeft: "1rem" }}
          contentEditable={!task.isDone}
          suppressContentEditableWarning={true}
          onKeyPress={(event) => {
            if (event.key === "Enter") {
              event.preventDefault();
              event.currentTarget.blur();
            }
          }}
          onBlur={({ target }) => {
            updateTask(task.id, {
              title: target.textContent,
            });
          }}
        >
          {task.isDone ? <s>{task.title}</s> : task.title}
        </span>
      </span>
      <Trash onClick={() => deleteTask(task.id)} />
    </li>
  ))}
</ul>

Footer Area (Refetch Button and LastUpdate Info)

<footer>
  <Button onClick={fetchTasks}>
    <ArrowsClockwise />
    Refetch
  </Button>
  <Typography.Paragraph>
    Last synced{" "}
    {new Date(lastUpdatedAt).toLocaleTimeString("en-US", {
      seconds: "2-digit",
      minutes: "2-digit",
      hours: "2-digit",
      day: "2-digit",
      month: "long",
      year: "numeric",
    })}
  </Typography.Paragraph>
</footer>