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, MVC also did not start as a type of framework or web architecture.
In this article, we find out how MVC concepts used at the component level can add better structure, split responsibilities, and reduce the complexity of React projects. We talk about the dance between the Model and the View, a dance that can add value to your apps.
All code examples are available in this GitHub repository.
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].
The term mental-model stuck with me because it represents the essence of what we do in our applications. We present and gather information in a way that a user can digest and send that data 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 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.
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.
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 simplified 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.
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 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 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.
The newsletter subscription app
Now, we will go up 10000 feet (ca. 3 km) to apply Model View Controller elements to the implementation of a React app.
We will implement the MVVM (Model-View-View-Model) interpretation of MVC; a Model and a View doing a nicely choreographed dance, with the Controller just setting the stage.
We will build an app where users can subscribe to newsletters.
Subscribing to a newsletter
Following the guidelines found in the brilliant book Writing Effective Use Cases written by Alistair Cockburn, we are ready to define the use case (user story) for subscribing to a newsletter.
Main success scenario
- The User visits the app;
- The System displays the form which enables him to subscribe to newsletters;
- The User fills in his full name;
- ~ selects a newsletter from the set of options;
- ~ subscribes to that newsletter;
- The System updates a list with all existing subscriptions.
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.
Put yourself in the shoes of a user. How would you imagine an app based on the above user story?
You would need to fill a form with your full name and select the desired newsletter. Then, you would click subscribe, and your subscription would appear in a list.
Here is my interpretation:
In this article, we will concentrate only on the form.
With our engineering hat on, we can convert the mental-model into a data structure describing our Model.
I know we will have an async operation to retrieve the list of newsletters from the server, and the isLoading boolean will help us display a loader to signal this event to a user.
We have the two fields, fullName, and newsletter, the latter holding the newsletter options, a conversion of the newsletter list into HTML select options.
Finally, the isValid flag will allow us to prevent submitting invalid user data and adhere to the requirements.
There are a few challenges we need to overtake:
- create a form component for collecting user data;
- retrieve a list of available newsletters from the server and convert them to select field options;
- validate the form;
- send the information for the rest of the app to consume and display the list of subscriptions.
As you can see, we have a mix of responsibilities (data retrieval and mapping, cross-cutting concerns related to component communication, validation, etc.).
Let us discover how the MVVM pattern will help us structure our code in a clear, extendable, and maintainable way.
The main job of the Model is to supply the mental-model to the View with just one prop.
The Model is a choreographer and a HOC (higher-order component); it knows to fetch the newsletters from the server and convert them to select field options for the View, informing the latter when loading is complete.
The Model knows how to validate the data when a user changes a field and what to do when a user clicks the submit button; it knows how to send the newsletter subscription to the Context, to be consumed and displayed by the subscriptions list component.
To easily manage its state, the Model uses the newsletterFormReducer.
In their dance, the Model is a gentleman and supplies a single object, the mental-model, for the View to display.
The View uses the isLoading boolean to show the loader until the newsletter field of the Model gets populated; it knows to keep the submit button disabled while the Model is invalid.
Elegantly, the View lets itself guided and gives user feedback by calling the handleSubmit and handleFieldChange methods, informing the Model about the gestures of the user.
Remember how the Observer pattern gets initialized?
The Controller sets the stage for the MVVM dance. It couples the View to the Model and can eventually pass props or do other complex config operations.
We did not reinvent the wheel; we separated the responsibilities of the code elegantly; we gained structure, improved the maintainability of our code, and prepared for future complexity.
We have a:
- View responsible only how things look, caring about a user, his gestures, and happiness;
- Model that handles all complex logic and passes a single object to the View, a snapshot that describes the UI at that moment in time;
- Controller that setups the interaction between the two main actors.
As I mentioned, all code examples are available in this GitHub repository.
A final treat
Googling around, I have found a talk of Trygve, 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.
Thank you for taking this journey together with me!