(Functional Programming) and <React/>

Alex R
Trainline’s Blog
Published in
6 min readFeb 26, 2017

--

This article is a translation of a presentation I recently gave at Trainline. I don’t claim to be an expert in functional programming but this is what I know after 1 year of programming in React using functional programming.

I see functional programming as a subset of tools and rules that we can use to build software in order to achieve the following:

  • Have a total control of the data flow
  • Achieve isolation of concerns
  • Achieve reusability in our software
  • Improve readability

We can easily reach these points by applying some basic rules. Luckily the following ones are part of the core concept of React.

  • Pure functions
  • Function / Component composition
  • Immutable operations, copy over reference

At first I would like to talk about Pure function and show you how we can follow this first rule and avoid any side effects.

(Avoid) => side effects

The first thing to ban in functional programming is side effects which can lead to unexpected behaviour while your program gain in complexity. This mean, it is better to avoid the mutation of variables outside of the scope of your function. Therefore we tend not to use foreach to iterate over an array:

const Journey = ({ journey }) => (<div>{journey.title}</div>);const EmptyJourney = () => (<div>Nothing to see here</div>);let journeyItems = [];journeyIds.forEach(id => {
if (id !== null) {
journeyItems.push(<Journey journey={journeys[id]}/>);
} else {
journeyItems.push(<EmptyJourney/>);
}
});

And prefer the pure equivalent:

const Journey = ({ journey }) => (<div>{journey.title}</div>);const EmptyJourney = () => (<div>Nothing to see here</div>);const journeyItems = journeyIds.map(id =>
id !== null ? <Journey journey={journeys[id]}/> : <EmptyJourney/>
);

If you have heard about functional programming you probably know these side-effects free array operators: map, reduce and filter. Let’s see how we can use them in a React application, assuming our data look like this:

// Data
const journeys = {
‘1’: { id: ‘1’, title: ‘journeyTitle1’ },
‘2’: { id: ‘2’, title: ‘journeyTitle2’ },
‘3’: { id: ‘3’, title: ‘journeyTitle3’ },
‘4’: { id: ‘4’, title: ‘journeyTitle4’ }
};
const journeyIds = [‘1’, ‘2’, ‘3’, null];

And we want to display only the journey that are not empty, we can do the following:

const filteredJourneys = (
journeyIds
.filter(id => id !== null)
.map(id => <Journey journey={journeys[id]}/>)
);

We can even have a shorter filter

journeyIds.filter(Boolean)

This way we get back an array of Journey component ready to be mounted. Without speaking about component let’s imagine we want to filter our null journeyId and return a Map structure of our journeys, we could then apply a reduce:

const mappedJourneys = journeyIds
.filter(Boolean)
.map(id => journeys[id])
.reduce((acc, next) => acc.set(next.id, next), new Map());

All these operators are side effects free because they imply that you write pure functions if you implement it correctly. Let’s have a look at what is not a pure function:

let initialState = 1;
const impurFunction = () => initialState += 1;

Here the problem is that we mutate initialState2 which is outside of the scope of the function, we could also write it this way:

const initialState = 1;
const pureFunction = () => initialState + 1;

Which is much better as we don’t mutate initialState, but it is even better to inject the state as a parameter of our function:

const purePlusOneFunction = (state) => state + 1;

Writing pure functions permit us to apply a couple of important functional programming concept. If you are developing a React application you probably have used two of them :

Compose | Memoize

Composition allow us to cut a big problem into very reusable small problems. Instead of having one big function, we cut it into multiple and composable functions, this way we can reuse them and it is easier to maintain. Form validation is a good candidate for composition, luckily we have a very good library for that: Revalidate.

So what is composition ?

Well if we use our previously defined, pure function “pureFunctionEnhanced”, we could use composition like this:

const plusTwoFunction = compose(
purePlusOneFunction,
purePlusOneFunction
);
const Three = plusTwoFunction(1);

There are many good explanation of composition out there, to make it short, the functions are composed so the result of the previously called function is passed as an argument of the next function.

Nice right ?! Well this apply to React component, when you call a component inside another one, you basically compose them, I would suggest you to refer to the React documentation for details.

Let’s have a look at memoize

We usually apply memoize when we have a performance concerns, the args of your function will be used to generate a hash, if all the args are the same between 2 calls of your function then it return the result previously cached, this is true only if your function is pure.

Note that this play well with primitive type, usually memoize implementation in Javascript (ramda, reselect …) only check for reference equality when it come to an array, an object or a function.

In a react application we could do something like this to memoize our components and avoid some unnecessary re-rendering:

import { memoize } from ‘ramda’;const journeyFn = memoize((key, code) => (
<div key={key}>{ code }</div>
));
const journeys = ({ journeys }) => (
<div>
{
journeys.map(journeyFn)
}
</div>
);

but it is actually not a good idea, because React already integrate the same concept if you use the PureComponent class.

export default class Journey extends React.PureComponent {
render() {
return (
<div>{ this.props.code }</div>
);
}
}

It will automatically define a shouldComponentUpdate function with a shallow equality check that we could define like this:

const shouldComponentUpdate = (nextProps) => (
Object.keys(this.props).reduce((res, k) => (
res && this.props[k] === nextProps[k]
), true)
);

If you have a stateless component you can use a High Order Function, Recompose already provide you one that you can use like this:

import { pure } from 'recompose';const Journey = pure(({ code }) => (<div>{ code }</div>));

This way if code is the same string then the evaluation nextProps.code === this.props.code will evaluate to true and the component will not re-render and keep it’s current state.

Let’s talk […immutability]

An immutable object or array cannot have it’s value changed after creating it. In javascript there is a very famous library to achieve this efficiently and safely called ImmutableJs but you don’t need it, you can accomplish the same in vanilla Javascript with some disciplines.

For example, this is what we want to avoid:

const mutableArray = ['1', '2', '3', null];
mutableArray.push('5');
// mutableArray => ['1', '2', '3', null, '5']

In order to make it as an immutable operation, we need to return a copy of our array:

const array = ['1', '2', '3', null];
const newArray = [...array, '5'];
// array => ['1', '2', '3', null]
// newArray => ['1', '2', '3', null, '5']

In order to achieve the same purpose on a object the ES6 spread operator come again very handy, so the operation is quite similar:

const dictionary = {
'1': {},
'2': {}
};
const newDictionary = {
...dictionary,
'3': {}
};

But again we can use our favourite array operators I talked about before because they all return a copy of the array we iterate over. So it is safe to use map, reduce, filter depending on what we want to achieve.

This is a very important notion to keep in mind when we plug Redux to a React application, it is essential to return a copy of your state in your reducer otherwise you might end up with some weird mutations of your data.

Conclusion

I have been very fast on each topic but the goal is to only give a quick overview of the essential functional programming concepts applied to a React application.

If you wish to go further with these concept I advise you to have a look at the following resources:

--

--

Alex R
Trainline’s Blog

Fullstack engineer @kaluza. Functional programming enthusiast.