Elements of MVC in React

Daniel Dughila
The Startup
Published in
7 min readNov 23, 2020

--

Back in June 2013, on the official Reactjs.org blog, there was mentioned:

React isn’t an MVC framework. React is a library for building composable user interfaces.

While this is true, the term MVC framework is more of a marketing term. The original MVC concept is closer to a design pattern and has nothing to do with frameworks.

Let’s discover the original MVC pattern and how, applied to React components, can significantly improve their structure and reduce their complexity.

A bit of Model View Controller history

Trygve Reenskaug discovered MVC at Xerox PARC in 1978.

The essential purpose of MVC is to bridge the gap between the human user’s mental model and the digital model that exists in the computer [Trygve Reenskaug].

http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html

The term mental-model stuck with me because it represents the essence of our applications. We present and gather information through an interface that a user understands, with the scope of sending it to the backend for processing.

The Observer pattern

MVC is the first implementation of the Observer pattern, a fact I discovered reading about this pattern in the Gang of Four book.

The first and perhaps best-known example of the Observer pattern appears in Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalk environment. MVC’s Model class plays the role of Subject, while View is the base class for observers [Design Patterns: Elements of Reusable Object-Oriented Software, 1994].

The Observer pattern is quite simple to implement.

The Subject

The Subject keeps an internal list of Observers, exposing an API to attach and detach them from the list.

Reacting to change, the Subject iterates over each Observer, calling their update method with the changes.

The Observer

Each Observer needs to have an update method called by the Subject.

On instantiation, the Observer gets a reference to the Subject, a reference used to check that the caller is indeed the correct one.

Main

The Main class instantiates the Subject and, after instantiating the Observer, attaches it to the Subject.

A basic MVC implementation

To set the ground for using Model View Controller elements in our React apps, we explore a vanilla MVC implementation of the Observer pattern, a Counter application; a box containing the value 0, incremented by 1, every time a user clicks a button.

The Model

For a user, the mental-model would look like this:

A programmer would convert it to just a few lines of code:

Just how Trygve Reenskaug envisioned, we see how MVC creates a bridge between the user mental-model and the digital one.

The Model is a Subject. The attach method assigns the View to it, and the incrementCounter method calls notify to update the View with the new counter value.

The View

The View presents the Model to the user in a friendly way.

The View has no logic; it registers the DOM elements, calling the incrementBtnClick method when a user clicks the actual button.

The Controller

The Controller understands user interactions, converting them into commands against the Model.

When initialized, the Controller instantiates the Model, creates a View instance, passing the handleIncrementBtnClick callback to it, and attaches it to the Model.

When a user clicks the increment button, the handleIncrementBtnClick method gets called, and the Controller commands the Model to increment the counter, notifying the View to display the changes.

Steps to apply MVC to any React component

With just a few steps, we can make any React component use the MVC pattern:

  1. give the main React component the Controller role;
  2. extract all JSX markup from the Controller into a separate View component;
  3. group the whole state from the main React component and move it to a single Model object;
  4. serve the Model to the View as the single source of data;
  5. make the Controller handle all the events fired by the View;
  6. use state manipulation mechanisms like the useReducer hook to operate on the Model in an encapsulated way.

Applying MVC to a complex React form component

Now it is time to apply the MVC pattern in practice to the form component of a React app, where users can subscribe to newsletters.

The newsletter subscription app

The application user story follows the guidelines found in the brilliant book Writing Effective Use Cases written by Alistair Cockburn.

Primary actors

  • User
  • System

Main success scenario

  1. The user visits the app;
  2. The system displays the form which enables him to subscribe to newsletters;
  3. The user fills in his full name;
  4. ~ selects a newsletter from the set of options;
  5. ~ subscribes to that newsletter;
  6. The system updates a list with all existing subscriptions.

Extensions

3.1. The user does not fill in his full name;
4. The system keeps the subscribe button disabled.

3.2. The user fills a full name shorter than 3. characters;
4. The system keeps the subscribe button disabled.

4.1. The user does not select a newsletter;
5. The system keeps the subscribe button disabled.

The form

The way the user perceives and uses the form represents its mental-model.

The form displays a loading indicator instead of the newsletter field until the newsletter options are available; it disables or enables the subscribe button depending on its validity.

The form’s implementation

The NewsletterForm package (folder) consists of the:

  • form component, playing the role of the Controller;
  • NewsletterFormModel;
  • NewsletterFormView;
  • NewsletterFormActions, an object encapsulating all the actions that the Controller can dispatch on the Model;
  • newsletterFormReducer, a function used to operate changes on the Model.

The Model

With our engineering hats on, we convert the form mental-model into an actual data structure, the NewsletterFormModel, with the main job to describe the form state at any moment in time.

Because retrieving the list of available newsletters from the server is async, the isLoading boolean will help us display a loader to signal this event to the user.

The fullName and newsletter fields hold the user data. The newsletter field also keeps the newsletter HTML select options.

Finally, the isValid flag allows us to prevent submitting invalid user data.

Managing the state of the Model

In newer React versions, there is a useReducer hook that lets us store our Model in memory; similar to Redux, we then use actions and a reducer to change it.

The NewsletterFormModel, actions, reducer, and helper functions, converge into a single unit with the Model role.

The actions

These are two main actions, setNewsletterOption, and changeField that the Controller dispatches to the reducer to change the Model.

The reducer

When the setNewsletterOption action gets called, the reducer sets the isLoading boolean to false and attaches the newsletter options.

When the changeField action gets fired, the reducer validates and modifies the fullName and newsletter fields with user data.

Helper functions

We have extracted the withValidation helper function to show how complex reducers can be simplified.

The isNewsletterFormModelValid function does the validation, checking that the fullName is longer than two characters and that the newsletter field has an option is selected.

The View

The View gets the NewsletterFormModel from the Controller through a single prop, making the code easier to reason about, especially with a View packed with Material-UI elements.

The isLoading boolean allows the View to show a circular loader until the newsletter field options for the dropdown get populated.

The isValid flag disables the submit button when the Model is invalid.

The handleFieldChange method is called by the View when the form captures user data.

The handleSubmit gets called when the user subscribes to a newsletter by clicking the submit button.

The Controller

The NewsletterForm component has the role of the Controller and it:

  • couples the View to the Model;
  • dispatches the setNewsletterOptions action with the newsletter options;
  • supplies the handleFieldChange function to the View’s onFieldChange callback;
  • calls changeField with the user data collected by the View;
  • passes handleSubmit to the View’s onSubmit callback;
  • calls the onSubmit function of the parent component with the user’s full name and selected newsletter.

We did it!

We learned about the original MVC and how we can improve our React components using this pattern so that we have:

  • a View responsible only about how things look, caring about the user, his gestures, and happiness;
  • a Model as the single source of state of the component passed to the View;
  • a Controller that setups the interaction between the two main actors and communicates with the outside world.

All code examples are available in this GitHub repository.

A final treat

Googling around, I have found Trygve’s talk, Re-thinking the foundations of object orientation and of programming, which quite frankly enlightened me; such a beautiful overview of how our industry evolved since 1970.

I would love to hear your thoughts!

Comment here or message me directly on Twitter or LinkedIn and, of course, if you liked the article, share it!

--

--