What If React Was Really Only The V in MVC?

A. Sharif
JavaScript Inside
Published in
10 min readFeb 11, 2017

This is a summary of a talk entitled “Functional Front-End Architecture” that I gave at React Vienna on 10.02.2017.

Thanks to Patrick Stapfer for the picture.

Initially React was positioned as the “V in MVC” in a time when AngularJS was the mainstream framework on the front-end. A couple of years after Pete Hunt’s talk, where he proclaimed “Never give up simplicity for familiarity”, React and it’s underlying paradigms are the de-facto standard for building modern web applications.

But what about the original “React is only the V in MVC” statement, is it still valid?

Also, the term “Virtual DOM” has been dropped along the way, which makes sense obviously. Just think about React Native in this context, which didn’t exist back when React was introduced to the public. React was a solely DOM based library at first.

This diff is a compact summary. Shifting from “Just the UI” to “Declarative”.

On a side note: Obviously this is also marketing, at least these changes have been committed to the repository within a div with class “marketing-row” and another div with class “marketing-col”.

But let’s get back to the original idea that React is really just the V as in view for a moment. To get a better ideas for the current status quo in modern UI development, let’s find some common ground first.

“The Virtual DOM concept has been often emphasized for its speedy performance. But there is also an equally important — if not more important — property. The concept makes it possible to represent the UI as a function of its state.”

Yassine Elouafi, Functions Everywhere

There’s something very important in the above quote. React introduced a way to write the UI as a function of its state. While the focus and discussions surrounding React mainly revolved around JSX and the Virtual DOM plus optimizations, think diff mechanisms. In his classic Pure UI post, Guillermo Rauch identifies what we gain by thinking of our UI as a function of application state.

„The fundamental idea I want to discuss is the definition of an application’s UI as a pure function of application state.“

Guillermo Rauch, Pure UI

So I guess we can agree on the fact that the UI as a function of its state is a sane way to think about our applications. Now how does this all fit with React really being only the view? Let’s take a look at Elm for a moment.

“We do not think in terms of reusable components. Instead, we focus on reusable functions. It is a functional language after all!”

Scaling The Elm Architecture (https://guide.elm-lang.org/reuse/)

So in Elm we don’t think about components, instead everything is a function. But if everything is a function how do we know when a component did mount? How do we handle asynchronous side-effects? How do we take care of routing? If everything is a stateless function by default (in React), we can’t do any of the above or can we? Redux already partly solves some of the problems, but as André Staltz pointed it out in his Some Problems with React/Redux blog post, it’s not completely solved.

Placing async effects also often happens in some component’s componentDidMount, which is an ad-hoc solution, since components are often meant only for view concerns (markup). It often feels incorrect to mix these concerns since Redux and its async solutions are meant to separate them from markup.

André Staltz, Some Problems with React/Redux

Let’s rephrase for a second. In React we talk about components and we differentiate between presentational and container components as a means to separate concerns and for better reusability.

You’ll find your components much easier to reuse and reason about if you divide them into two categories. I call them Container and Presentational components* but I also heard Fat and Skinny, Smart and Dumb, Stateful and Pure, Screens and Components, etc.

Dan Abramov, Presentational and Container Components

Further Redux has taken some inspiration from the Elm Architecture for handling state management and making state changes predictable.

Elm inspiration in Redux

Following these principles we can already write consist applications, that are testable and partly reusable. But let me ask this question: What if didn’t a have to think about the fact if a component is presentational or a container?

How can React only be the V?

From here on out, when we mention React, we could mean Inferno, Preact or other DOM based libraries, from here on out we will not care about any lifecycle methods (sans shouldComponentUpdate), we will use React as a view function.

We’ll be using elmar.js, a playground for experimenting with an Elm inspired architecture, to show how we can think about our architecture.

The infamous Counter Example

If you have kept on reading up until this point, you’ll probably know how to implement the counter example in redux. If not, here’s the link to the example. But what if we wanted to reuse Counter? What if we need to create a dynamic list of Counters or another Component for that matter? How do we solve this in redux? Before you go ahead to implement the list, let me show a different way to think about how to structure our applications.

Let’s remain with the Counters, as it’s basic enough to not distract from what we’re trying to understand. To kick things off, let’s assume we have a counter.js file.

First we’ll define an init function, which receives a model and returns a model.

export const init = count => count

Further let’s define an Action object containing an increment and decrement function, which again receive a model and return an updated model.

const Action = {
Increment: x => x + 1,
Decrement: x => x - 1
}

This is looking very similar to a redux reducer. Let’s also define how Counter should get updated, which normally would be applying the model on a action.

export const update = (action, model) => action(model)

And finally we will need a view. This is where we bring in React or a React-Like library.

export const view = (signal, model) => (
<div>
<button onClick={signal(Action.Decrement)}>-</button>
<div>{model}</div>
<button onClick={signal(Action.Increment)}>+</button>
</div>
)

Let’s not focus on signal for a moment, we’ll get into that soon. We can see that the view looks very similar to the one in redux. Finally we export our init, update and view functions.

export default {init, update, view}

Ok, so we have created a number of functions and exported them, you can find the code here.

Now what if we wanted to display two counters?

Up until now this should have been quite clear to understand. The interesting things happen from here on out. Let’s create a new file and name it CounterPair.js

Let’s import Counter.

import Counter from './SimpleCounter'

Next, let’s define how the CounterPair is initialized.

export const init = (top, bottom) => ({
top: Counter.init(top),
bottom: Counter.init(bottom)
})

We defined a model that contains a top and a bottom property, because we know how to initialize our Counter. So what we can notice here is how we compose inits to calculate the initial state for the CounterPair.

Same logic applies for our Action. We know how to update our Counter, as we have access to Counter’s update function, so again we’re composing functions. We also added Reset to reset enable resetting both counters.

const {update} = Counter

const Action = {
Reset: model => init(0, 0),
Top: (action) => model =>
Object.assign({}, model, {top: update(action, model.top)}),
Bottom: (action) => model =>
Object.assign({}, model, {bottom: update(action, model.bottom)}),
}

Our update function is the same as the one in Counter, nothing special here, we apply the model to the action.

export const update = (action, model) => action(model)

Finally, we’ll need to render the counters.

export const view = (signal, model) => (
<div>
{Counter.view(forward(signal, Action.Top), model.top)}
{Counter.view(forward(signal, Action.Bottom), model.bottom)}
<button onClick={signal(Action.Reset)}>Reset</button>
</div>
)

The interesting part is how we pass the correct Actions down to the counters. forward is actually just a compose.

const forward = (f, g) => (...xs) => f(g(...xs))

The Counter doesn’t need to know what action is actually being called inside CounterPair, we just call update on the correct Counter which dispatched the action.

Note: elmar offers a number of functions like forward and signal to support enforcing this architecture, but we’ll get into this in a moment.

Now what if we wanted to create a list or a CounterPairPair? Following this structure, we can achieve all of the above without having to apply any workarounds. Check the CounerPairPair or the CounterList examples.

Now that we have the basics out the way let’s see how we can render something to the screen.

Elmar/React

elmar has a reference implementation on how to start your application with React, you can find the code here. Note that this is a reference implementation and a great starting point for understanding how to either rollout your own implementation or optimize the existing solution.

import {render} from 'react-dom'
import
{start} from './elmar/react'

We will need react’s render function and start from elmar/react. Once we have imported the two functions we finally need to call start with our CounterPair.

render(start(CounterPair, 1, 5), document.getElementById('app'))

start also knows how to pass signal down as well handle any actions dispatched via signal. For a deeper understanding take a look at the source code.

Advanced

Up until now we haven’t dealt with side-effects or routing and other advanced features, so let’s see how this can be done without having to rely on React specifics.

Side-Effects

How do we tell our application to do an initial fetch when I want display a list of users f.e.?

If you remember, we passed in a model and returned a model when we wrote our init and action functions. But what if we would return a model and effects tuple instead?

Model -> [Model, Effects]

Let’s take a look at the RandomGifViewer example.

export const init = (topic) =>
[ {topic, gifUrl: null, error: false}
, [getRandomGif(topic)]
]

The init function now returns a model and an array carrying effects. In this specific case getRandomGif is a function that expects a topic string and returns a function awaiting to return a promise. We’re moving any side-effects to the edges of our application, as React doesn’t have to know what side-effects are being triggered. All React cares about is returning a representation of the UI. The same applies to any effects triggered via user interactions. For example we might have a button that calls an action, that needs to refetch any information from a server. Our view might look like this:

export const view = (signal, model) => (
<div>
<h4>Topic: {model.topic}</h4>
<div style={imgDivStyle}>
{model.error
? <span>Error/Not Found</span>
: viewImage(model.gifUrl)}
</div>
{!model.gifUrl && !model.error
? <span>Please Wait</span>
: <button onClick={signal(Action.RequestMore)}>More</button>}
</div>
)

And our Action could have a RequestMore function.

const Action = {
RequestMore: model =>
[{...model, gifUrl: null, error: false},
[getRandomGif(model.topic)]
],
...
}

Again, we’re returning a model/effect tuple. This principle applies when initializing our GifViewer as well as when a user interacts with our application.

Bonus

Also take a look at the time-travel and the time-travel in time-travel examples. Once you structure your applications following these principles you get those benefits for (almost) free.

realtime replay with elmar.js

Routing

In most cases a switch statement is enough. The view knows what Component to display. By making the route part of the model, we can easily display the correct Component by matching with the route name defined in the model. I would recommend to take a look at how Elm solves navigation for deeper understanding or inspiration. Other interesting insights can be found in ClojureScript, think route matching libraries. Also you might want to take a look at my post Introduction To Functional Front-Ends With Inferno — Side Effects And Routing, where I go into more detail on how one might approach routing. Overall I think it’s a good idea to separate between the route matching, which can be handled by one library (f.e. myro) and the routing itself.

Optimizations

There is room for optimizations and improvement. This should be for another talk or write-up. First things first. We will need to build a large application with these ideas and start to optimize from there on out.

Outro

Take a look at the elmar.js reference implementation and examples for inspiration. The ideas presented here should be seen as an inspiration, not as a redux alternative. There are multiple aspects to consider when deciding on a language, library, framework, tools. The current eco-system, the current knowledge inside an existing team, business aspects and many more. Multiple aspects have to be considered when deciding on a solution. Nothing is in isolation.

Summary

Here’s a quick recap and take away.

Rethinking about what we know.

Reflection.

Sometimes less is more.

Not everything has to be a React Component.

Special Thanks to…

Oskar Maria Grande, Günter Glück, Andrey Okonetchnikov, Nik Graf, Patrick Stapfer, Glenn Reyes, Karl Horky and everyone involved with React Vienna or provided inspiration or direct feedback.

Edit (21.02.2017): Video of the talk is available.

For questions or feedback find me on Twitter.

--

--

A. Sharif
JavaScript Inside

Focusing on quality. Software Development. Product Management.