Purifying React (with annotations)

A presentation at React Berlin in May 2016 in Berlin, Germany by Robin Pokorny

Slide 1

Slide 1

Purifying React @robinpokorny Hi, I will share how we made our front end pure, by incrementally introducing Redux, ImmutableJS, and higher-order components, all under constant requests for new features.

Slide 2

Slide 2

I’m Robin and I met React on the internet. We’ve been together since. Nine months ago I joined Wimdu to help it with its front end.

Slide 3

Slide 3

Our site is server-rendered by Rails. We have a growing number of async-loaded independent React components to enhance the page.

Slide 4

Slide 4

The problem occurred when we wanted them to communicate amongst themselves. We decided to implement a state container— Redux.

Slide 5

Slide 5

We only introduced Redux when we felt we needed it. We try to avoid premature optimisation and over-engineering. As we were aware that a rewrite would be too big, paralysing us for weeks we came up with an incremental process. Now, we need to purify our code base… ‘Pure’ is a concept in functional programming (learn more).

Slide 6

Slide 6

this to params Function We start purifying at the bottom—individual functions. This was not a project or task. We only refactored code we were touching during our regular work.

Slide 7

Slide 7

Old renderGroups() { const { groups } = this.props … } New renderGroups(groups) { … } instacod.es/107138 First step was easy. Get rid of this and pass data in parameters. Only lifecycle function could still access this.

Slide 8

Slide 8

const addParam = (options, name, param) => { options[name] = param } const addParam = (options, name, param) => { return Object.assign( {}, options, { [name]: param } ) } instacod.es/107139 Second step proved to be more challenging. Instead of changing the object, function should return changed object without modifying the original.

Slide 9

Slide 9

state to props Component this to params Function When all functions in a component are pure, we make the component pure, too

Slide 10

Slide 10

propstate This means a component should only depend on its props. Everything in state was moved to the parent component’s state and passes down via props.

Slide 11

Slide 11

const MyComponent = ({ steps, modifier = ” }) => ( <div class={ modifier }> … ) MyComponent.propTypes = { steps: PropTypes.arrayOf( PropTypes.shape({ completed: PropTypes.bool.isRequired, title: PropTypes.string.isRequired }) ).isRequired, modifier: PropTypes.string } instacod.es/107140 This is how an ideal pure component looks like. Note that we are thoroughly describing propTypes. They serve also as a documentation.

Slide 12

Slide 12

all in state Container state to props Component this to params Function Now when all children components are pure we can make the top-level container pure too. Only this container is aware of the data flow.

Slide 13

Slide 13

import MyComponent from ‘./my-component’ class Wrapper extends React.Component { constructor() { this.state = { active: false, list: [], }; } open() { … } close() { … } } render() { return (<MyComponent {…this.state} onOpen={this.openScratchpad.bind(this)} onClose={this.closeScratchpad.bind(this)} translations={this.props.translations} />) } export default Wrapper instacod.es/107146 All data is inside this container’s state. Modifications are possible only with provided methods. MyComponent passes these ‘actions’ further.

Slide 14

Slide 14

state to store all in state Redux Container state to props Component this to params Function Introducing Redux is now easy. We have the data structure described. All components keep their APIs (= propTypes).

Slide 15

Slide 15

instacod.es/107141 We can remove the Wrapper and connect to Redux. Data is now in store, actions correspond to methods. MyComponent has not changed.

Slide 16

Slide 16

react-rails As mentioned earlier, our app is in fact Rails app. The react-rails gem enables mounting React in templates. It also passes data from Rails to the component.

Slide 17

Slide 17

instacod.es/107136 To pass the initial state (from multiple templates) we serialise it to JSON and append it to the array. Component is referenced by (global) variable name.

Slide 18

Slide 18

import tx from ‘transform-props-with’ if (!window.Wimdu.store) { const initialState = Map().mergeDeep(…window.INITIAL_STATE) window.Wimdu.store = configureStore({ initialState }) } window.Wimdu.MyComponent = tx({ store: window.Wimdu.store })(MyComponent) instacod.es/107137 Thanks to ImmutableJS deep merging method we combine all partial states into one store. We use tx to pass this store to the component.

Slide 19

Slide 19

immutable state to store all in state Redux Container state to props Component this to params Function Changing data handling in Redux we introduce immutable structures (e.g. ImmutableJS). No need to touch anything else.

Slide 20

Slide 20

instacod.es/107142 This is an example Redux reducer. Thanks to Records we have structure consistency, documentation, and dot access notation.

Slide 21

Slide 21

instacod.es/107143 Unfortunately it is difficult to have Record of Records. For namespacing we combine reduces the usual way. Leafs (and only leafs) of reducer tree are Records.

Slide 22

Slide 22

instacod.es/107144 To ensure backwards compatibility we convert immutable structures to simple JS objects at first. We ‘immutablyfy’ a component passing JS to its children.

Slide 23

Slide 23

immutable state to store all in state Redux Container state to props Component this to params Function First we went UP—purifying from smallest parts. We introduced immutability at the top and went back DOWN. Download this slide as a one-page summary: http://buff.ly/1XbtFpH

Slide 24

Slide 24

A secret tip for better React and Redux apps: ‘How would I do it in Elm?’ I am fond of Elm (although not on the production now). It helps me to decide how to structure the app. Both React and Redux are inspired by Elm.

Slide 25

Slide 25

@robinpokorny me@robinpokorny.com I want to thank my team for their hard work. You made this happen! Any question or feedback is welcomed.

Slide 26

Slide 26

This work by Robin Pokorny is licensed under a Creative Commons Attribution 4.0 International License. Cover image was taken from 1952 Kaiser Aluminum ad: http://www.fulltable.com/vts/f/fut/f/world/SH536.jpg Image on the last slide is taken from a postcard: Fission Room, Niagara Mohawk Progress Center, Nine Mile Point, NY