DEV Community

Cover image for MVVM in React: A beginner's guide
Makanju Oluwafemi
Makanju Oluwafemi

Posted on

MVVM in React: A beginner's guide

Introduction

It is essential for all developers to give attention to code organization while creating modern software, since it may significantly improve developer experience, collaboration, code maintainability, and reuseability. There are different architecture that can be used to archive this. however, this article describe how to apply the Model-View-ViewModel (MVVM) architecture to improve the organization of your code.

The Model-View-ViewModel (MVVM) software architecture model is widely used in the development of user interfaces. The user interface is divided into three components that are linked together: model, view,  and view-model. This architecture promotes separation of concern. Without closely tying the user interface to the underlying business logic, it allows developers to work on multiple portions of the application at the same time. 

Understanding MVVM

MVVM is a design pattern that promotes separation of concern in frontend development by separating the UI, application data, and business logic. This method makes sure that the presentation logic and application data can be managed independently, making the codebase more modular and maintainable.

Components of MVVM

Model

The model represents the application's data and business logic. It's made of the data structures and logics that contain how the application behaves. What I'm saying is that the model serves as the foundation upon which the entire application is built. It is also responsible for retrieving and manipulating data, as well as enforcing business logic.

View

The view is responsible for presenting the user interface to the end-user. The visual elements, such as buttons, text fields, and layouts, are kept in this part of the application; they are what users interact with. Unlike traditional architectures where the view is tightly coupled with the underlying logic, MVVM advocates for a more decoupled approach. The view should be passive and only concerned with rendering the view model's state.

ViewModel

The ViewModel acts as an intermediary between the view and the model. It serves as a bridge that translates the data and actions of the model into a format that the view component can understand. The View may stay portable and concentrate only on display issues because the ViewModel handles state management and presentation logic. They are connected by a channel, allowing them to exchange messages when necessary. This strategy promotes improved concern separation.

Implementing MVVM in React

In this example, we will create a walk-through guide on how to implement a simple feature where users can add and list tasks. 

To get started, you should have a fresh installation of react ready. or better still copy and paste this code on your terminal to create a project.

 

npx create-react-app mvvm-app
cd mvvm-app
Enter fullscreen mode Exit fullscreen mode

After setting up, the first thing to do is create a model for managing data and business logic.

Create a file called TaskModel.js, copy and paste this code into it.
TaskModel.js

class TaskModel {
  constructor() {
    this.tasks = [];
  }

  addTask(task) {
    this.tasks.push(task);
  }

  getAllTasks() {
    return this.tasks;
  }
}

export default TaskModel;
Enter fullscreen mode Exit fullscreen mode

This code has a TaskModal class, which consist of a constructor that is used for saving all the task in an array. It also consist of two methods, addTask and getAllTasks both of which are for adding and getting tasks.

Next, you will create the render view using App.js and show 
App.js

import React, { useState } from "react";
import useTaskViewModel from "./view-model/TaskViewModel";
import "./App.css";

function App() {
  const [taskInput, setTaskInput] = useState("");
  const [tasks, setTasks] = useState([]);
  const { addTask, getAllTasks } = useTaskViewModel();
  const handleAddTask = () => {
    addTask(taskInput);
    setTaskInput("");
    setTasks(getAllTasks());
  };

  return (
    <div className="main-container">
      <h1>Task Manager</h1>
      <div className="main text-input">
        <input
          type="text"
          placeholder="Add a task"
          value={taskInput}
          onChange={(e) => setTaskInput(e.target.value)}
        />
        <button onClick={handleAddTask}>Add Task</button>
      </div>
      <div className="main task-wrapper">
        <h2>Tasks</h2>
        <hr />
        {tasks.length !== 0 && (
          <ul className="task-item">
            {tasks.map((task, index) => (
              <li className="text" key={index}>
                {task}
              </li>
            ))}
          </ul>
        )}
        <div className="list-not-found">
          {tasks.length === 0 && <p className="text">There is no task item</p>}
        </div>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This component serves as the main interface for the tasks. It utilizes local state to manage the input for adding tasks taskInput and the list of tasks tasks. The useTaskViewModel hook is used to interact with the ViewModel, enabling functionality for adding tasks addTask and retrieving all tasks getAllTasks.

The handleAddTask function add task to the list, triggering the addTask method with the current input value, resetting the input field, and updating the task list with the newly added task. The JSX markup structures the UI with input and button elements for adding tasks and a list to display existing tasks. Conditional rendering ensures that a message is displayed when there are no tasks present.

App.css

body {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.main-container {
  background: #20205d;
  padding: 10px 20px;
  border-radius: 5px;
}
.main-container h1 {
  font-size: 16px;
  color: #eee;
}
.main-container button {
  border: 1px solid white;
  padding: 7px;
  border-radius: 5px;
}
.text-input {
  display: flex;
  flex-direction: row;
  gap: 2px;
}
.text-input input {
  border: 0.78px solid #eee;
  display: flex;
  padding: 7px;
  border-radius: 7px;
  gap: 2px;
}
.text-input button {
  width: 100px;
}
.task-wrapper h2 {
  font-size: 16px;
  color: #eee;
}
.text {
  color: #eee;
  font-size: 14px;
}
Enter fullscreen mode Exit fullscreen mode

In this CSS code example, we style the task management application's user interface. We center the content, define the main container's appearance, and set styles for text, input fields, buttons, and task items.


output-1


This image shows the output of the image. task has not been added in this image but the HTML(HyperText Markup Language) and CSS(Cascading Style Sheets) has been implemented.
TaskViewModel.js

import { useState } from "react";
import TaskModel from "../model/TaskModel";

const useTaskViewModel = () => {
  const [taskModel] = useState(() => new TaskModel());
  const addTask = (task) => {
    taskModel.addTask(task);
  };

  const getAllTasks = () => {
    return taskModel.getAllTasks();
  };

  return { addTask, getAllTasks };
};

export default useTaskViewModel;
Enter fullscreen mode Exit fullscreen mode

This useTaskViewModel hook contains all the logic, data, and functionality related to tasks. It uses the useState hook to initialize a TaskModel instance, which handles task-related operations. addTask Accepts a task as input and delegates the task addition functionality to the TaskModel instance, which adds the task to the internal data structure.

getAllTasks Retrieves all tasks stored in the TaskModel instance and returns them as an array. Components can easily interact with tasks using these methods without directly accessing the underlying data or business logic of the task model.


output-2


This image shows the output of the task user interface, when a user add task to it.

Benefits of Using MVVM in React

React applications can achieve even greater levels of modularity, maintainability, and testability. In this section, we'll explore how MVVM enhances the developer experience by focusing on three key benefits: separation of concerns, reusability and maintainability, and enhanced testability.

Separation of concerns

MVVM promotes separation of concern by giving each of its components distinct roles. The model represents the data, business logic of the application, and state management. The visual elements that are visible to users are represented by the view, which is composed of JSX components that make up the user interface. Presentation logic is incorporated into the view model. It manages data transformation, ensuring that the data in the model and the view's display communicate with one another without interruption.

Reusability and maintainability

By isolating presentation logic in the ViewModel, developers can reuse this logic across multiple components without duplicating code. This promotes code reuse and simplifies maintenance efforts. Also, with clear separation between Model, View, and ViewModel, React applications built using MVVM are inherently more modular. It is easier to understand, change, and expand the functionality of the application when each component is able to concentrate on its own functions.

Enhanced testability

Developers can write unit tests for the ViewModel, which contains the application's business logic. These tests verify that the ViewModel behaves correctly under various conditions without needing to render the UI components. They can also write integration tests to ensure that the model, view, and view work together as expected. These tests validate the end-to-end functionality of the application, covering interactions between different layers. In essence, MVVM makes it easier to write good tests for React components. This results in software that is more stable, dependable, and bug-free.

Conclusion

Dependable systems that enhance testability, ensure code separation, and eliminate all code duplication are necessary while developing scalable applications. All these are what MVVM stands for. Look it over now, implement it in your project, and improve as a developer. Happy coding!

Top comments (0)