Functional Programming inside your SPA

Functional programming is one of the hottest topics in the JavaScript community these days. It is used by notable libraries, and numerous articles are published each month. This hype helps developers expand their knowledge of the paradigm. Knowing FP is becoming a standard requirement among developers.

However, when it comes to single page applications (SPAs), it seems like sources are limited. It is hard to find an article that describes the relationship between FP and SPAs. That is the reason why some developers are not quite sure where to use functional approach inside an SPA. This article tries to spot the right place for FP inside an SPA.

It is important to note that new syntax has been added to JavaScript in ES2015. It has made coding with functional programming a lot easier and helped JavaScript developers embrace the functional paradigm.

In my opinion, the most notable features promoting functional programming are the Arrow functions (“=>”), the new Array.prototype.* methods (e.g. map/filter/reduce) and the Object.assign() method.

Undoubtedly, JavaScript is becoming functional-friendly, which is great, but on the other hand, the most common architecture these days for SPAs is the component-based architecture. Components are classes, which are part of the OO methodology. So, the obvious question is: are components the right place for functional programming in an SPA? and if not, where should functional approach be used in an SPA?

Components are not the native place to use functional programming because their methods often have side effects — thus breaking the core principle of pure functions

The answer, in essence, is that components are not the right place to use FP in SPAs because component methods are usually impure. Their output often depends on the instance’s state, and given the same input, the output may not always be the same. They also often change the “outer world” state (e.g. setting an instance property using the ‘this’ keyword) or have unpredictable results (like when triggering an asynchronous call). Therefore, components are not the native place to use functional programming because their methods often have side effects - thus breaking the core principle of pure functions.

That said, this does not mean that functional techniques like map/filter/reduce functions cannot be used inside smart components (though a rule of thumb is to reduce the number of smart components in an SPA), it is just that development using the functional paradigm is about creating functions which are reusable, predictable and pure. It is about using those functions as building blocks - not just using functions that implement functional programming principles inside a component’s method (e.g. map/reduce/filter).

Now that those points are clear, let us review the core principles of functional programming to better understand what functional programming is:

  • Immutability - An immutable object is an object that cannot be changed nor modified. It means that every time something has to change, a new instance must be created with the new state.
  • Pure functions - A pure function is a function without side effects. It never changes the state in the “outside” world, its output is predictable, and given the same input, will always return the same output.
  • Higher-order functions - A function that either receives a function as a parameter and/or returns a function.

Remember that every SPA is divided into two main fields. One field is responsible for the UI and the presentation of data (which is done with components). The other field is responsible for managing data (often a single store with Redux or multiple stores with Flux/Mobx). The core principles of FP fit the needs in the field of data management. As for data management, you will always want to have a reliable source of truth, so it is crucial to keep the application’s state immutable and prevent the implications of mutating a shared state - this can be achieved by using pure functions. Using higher order functions will help you create reusable functions (by implementing concepts such as closure, function composition and curry) and keep things DRY.

If so, a perfect place for the functional approach has been found. The core principles of FP fit the needs in the field of data management. Data management is where functional approach thrives.

Data management is where functional approach thrives

Computing derivative data (through ‘selectors’) from the application’s state, and serving this data to components, is under the responsibility of the data management field, and therefore is indisputably the ideal place to use functional programming. As described above, this is where the principles of immutability and pure functions are so important.

If the “separation of concerns” principle is being enforced in an SPA, most of the functional code will be kept out of the components.

Code Example - Reveal the Issues

After we found the place for functional programming, it is time to see some code examples. Let’s create a simple todo app without the implementation of functional techniques, and then refactor it by using functional programming. It will reveal the problems that exist even in the smallest applications and how functional programming solves them. As a bonus, FP also makes the code look more elegant. The code sample is done with Angular and Redux, but the principles are the same for any other framework.

First, let’s create the reducer and the Store:

// store.ts
import { createStore } from 'redux';
const reducer = (state = Object.create({}), action) => {
switch (action.type) {
case 'ADD_TODO': {
return Object.assign({}, state, {
todos: state.todos.concat([action.payload])
});
}
case 'COMPLETE_TODO': {
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => (
index === action.id ?
{...todo, isCompleted: true} :
todo
)),
});
}
default: {
return state;
}
}
};
export const store = createStore(reducer, { todos: [] });

Now, let’s naively create a component to present the data:

// todo-list.component.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Unsubscribe } from 'redux';
import { store } from './store';
@Component({
selector: 'todo-list',
templateUrl: './todo-list.component.html',
})
export class TodoList implements OnInit, OnDestroy {
private reduxUnsubscribe: Unsubscribe;
private todos = [];
  ngOnInit() {
this.reduxUnsubscribe = store.subscribe(() => (
this.todos = store.getState().todos
));
}
  ngOnDestroy() {
this.reduxUnsubscribe();
}
  addTodo(content) {
const payload = { content, isCompleted: false };
    store.dispatch({ type: 'ADD_TODO', payload });
}
  completeTodo(id) {
store.dispatch({ type: 'COMPLETE_TODO', id});
}
  getCompletedTodosCount() {
return this.todos.filter(todo => todo.isCompleted).length;
}
}

Finally, create the template:

<div>
<h1>Todo List</h1>
<p>
{{ getCompletedTodosCount() }} Completed todos
</p>
<ul>
<li *ngFor="let todo of todos; index as index">
{{ todo.content }}
<button *ngIf="!todo.isCompleted"
(click)="completeTodo(index)"
type="button">
Complete
</button>
</li>
</ul>
<form (ngSubmit)="addTodo(content)">
<input type="text" [(ngModel)]="content" name="content">
<button type="submit">Add todo</button>
</form>
</div>

There are several issues with this code. First, there are no selectors (selectors are described in the solutions section below), the component is accessing the state directly. If a developer accidentally mutates the state (it happens), it will affect other components’ data - and that’s a disaster. Second, the component is now holding a reference to the state in its property. This breaks the principle that the state should not be part of the component. The component should be stateless. It should merely be a ‘pipe’ to present the data. Third, the component receives a notification for every change in the state, even for changes that are unrelated to it, which will often cause the subscriber function to be abused with logic in order to avoid unnecessary computing. Lastly, the “getCompletedTodosCount” method is computing data, that’s bad. If another component now needs the same data we will inevitably be forced to duplicate our code.

Code Example - Discussing Solutions and Recap

Those are pretty significant issues. This is just a small Todo application with a single reducer and a single component. Imagine what will happen in a larger application with dozens of reducers and hundreds of components. Those issues reveal that the state is not reliable in such an application. It is too prone to errors. Think about it, so many options for collisions, it’ll be a mess!

We have to find a solution that will keep the state immutable, so the process of computing derived data will not affect the original data. Also, despite multiple components requiring the same data, we should always avoid code duplication. One solution may be to use a third party library like ImmutableJS in order to make the state an immutable object. However, that is not the solution we’re going to discuss in this article because adding a third party library always involves overhead to the developer in the development process. In the case of immutability, it is actually redundant to add a third party library, as JS currently has very simple and flexible syntax to create new objects (the “Object.assign” method). So, using a native JS approach is a better solution when implemented correctly.

Personally, I find ‘selectors’ to be the most convenient approach to tackle these issues. A selector is simply a function that extracts or computes derived data from a given state. A selector will always return a new object, and will never mutate the original data. Hence, selectors should always be pure functions — thus keeping the concept of immutability. As a rule of thumb, organize the state management flow in your application using the concept of selectors to access the state and compute derived data.

It is relatively easy to add selectors to the data management flow in an existing application as no refactoring is required in the reducer.

Now, can you try to guess what methods in our Todo app should use selectors? The answer is “getCompletedTodosCount” and “getTodos”.

So let’s create our first selectors:

// selectors.ts
import { store } from './store';
export const getState = () => Object.assign({}, store.getState());
export const getTodos = state => [...state.todos];
export const getNumOfCompletedTodos = state => (
state.todos.filter(todo => todo.isCompleted).length
);

You can see that when a selector’s name is picked carefully, its purpose easily becomes self-explanatory. The ‘getState’ selector simply returns the state, ‘getTodos’ returns the todo list and ‘getNumOfCompletedTodos’ returns the number of completed todos.

At first, adding the selectors layer to the state management flow might seem a bit of an overkill, but the truth is that selectors really help to organize the state management architecture of an SPA. The UI is now decoupled from the state, and developers are no longer able to mutate it, even not by accident. The use of selectors also reduces code duplication because selectors can easily be written as reusable functions.

Let’s refactor the component to access the state through the selectors:

// todo-list.component.ts
import { getNumOfCompletedTodos, getTodos, getState } from './selectors';
import { Component } from '@angular/core';
import { store } from './store';
@Component({
selector: 'todo-list',
templateUrl: './todo-list.component.html',
})
export class TodoList {
addTodo(content: string): void {
const payload = { content, isCompleted: false };
    store.dispatch({ type: 'ADD_TODO', payload });
}
  completeTodo(id: number): void {
store.dispatch({ type: 'COMPLETE_TODO', id});
}
  getTodos(): any[] {
return getTodos(getState());
}
  getCompletedTodosCount(): number {
return getNumOfCompletedTodos(getState());
}
}

Note how the component seems cleaner now. It also does not hold its state and does not access the state directly. It can neither mutate it, nor affect other components.

In the template, only one line has to be changed:

<li *ngFor="let todo of getTodos(); index as index">
{{ todo.content }}
<button *ngIf="!todo.isCompleted"
(click)="completeTodo(index)"
type="button">
Complete
</button>
</li>

There’s one hidden issue in this implementation that selectors create. Each time there is a change in the application (e.g. when Angular runs its change detection or if another part of the state changes), the data is being computed. It means that even if nothing has changed in the relevant data, it still gets computed every time. This is where Reselect comes in.

Reselect is a library to create selectors that only compute data when something has changed in the particular part of the state. Therefore it will save the application from unnecessary computations.

First, let’s add Reselect by running yarn add reselect (or npm i reselect), then we can refactor our selectors to use Reselect:

// selectors.ts
import { store } from './store';
import { createSelector } from 'reselect';
const todosSelector = (state: {todos}) => state.todos;
export const getState = () => Object.assign({}, store.getState());
export const getTodos = createSelector(
todosSelector,
todos => ([...todos])
);
export const getNumOfCompletedTodos = createSelector(
todosSelector,
todos => todos.filter(todo => todo.isCompleted).length
);

The function ‘createSelector’ is a higher-order function that receives two or more functions as arguments. The first function(s) are called the input function, the last will always be the result function. In our example, the first function is the only input function and the second is the result function. The output from the preceding function will be the argument of the next function. So, the first function will extract the relevant object from the state and only if that object has been updated, the second function will be executed. No more unnecessary computations.

Although the selectors have changed, nothing has to change in our component nor in the reducer. Hurray!

In a bigger application, those selectors can easily be reused to serve different components. In a more complex application, you will create your own higher-order functions for common tasks to ease the data workflow, and probably create more complex selectors.

Our journey has come to its end. The state is now built on the core principles of functional programming: Immutability, Higher-order functions and Pure functions. Functional approach helped us solve the issues in an elegant way and is a great, reliable gatekeeper for our application’s state. Always follow the rule of thumb that components should be stateless, and reduce the number of smart components in your application. That way you won’t find yourself trying to implement functional techniques inside components. Combining both paradigms inside an SPA grants the best of both worlds.

Happy functional programming!