React and Redux the Hard FP Way

A more accurate but less informative title for this post would be “How I wish React and Redux were explained to me”. Note that this does not imply that this method of explanation is suitable for anybody else. I suspect it won’t be for most people.

I had to learn React and Redux the past summer for my internship at MemSQL, and there were hundreds of articles that explain React and Redux in addition to the (fine) built-in documentation, but none of them scratched the itch; I wanted to know what was going on completely, including some of the technical details and the philosophy I ought to be following, as well as efficiently. I did not need another explanation about how to think functionally, in JavaScript types or with immutable data. React’s chapter on Conditional Rendering, for example, felt so inefficient — I know what if statements and conditional expressions are, and I know how to refactor complicated subexpressions into variables…

So here’s the guide I wish I had. I think. It’s been months since I started it (as usual, for posts on this blog) and it is probably incomplete. However, I haven’t written React/Redux deeply in a while, so I didn’t have much motivation to continue to investigate the incomplete bits; and the perfect is the enemy of the good, so here it is.

React

A JavaScript library for building user interfaces.

React lets you build HTML interfaces declaratively. To understand React, let’s think about HTML tags as functions for a bit.

An HTML tag like div is basically a (pure) function type Tag<Props> = (Props) => HTMLElement, where Props (short for “properties”) is a type that describes the attributes the tag takes, and HTMLElement is a thing your browser knows how to render. For simplicity we include children : HTMLElement in the type of the prop instead of having it as a separate argument. If you translated <div id="foo">hello <b>world</b></div> into JavaScript, you might get something like div({ id: foo, children: ["hello ", b({ children: "bar" })]). Note that we implicitly use strings as HTMLElements, and we treated an array of HTMLElements as a single HTMLElement. JavaScript is weakly typed and React can figure this out.

Now, when you write “HTML tags” in JavaScript using React, you don’t actually get browser-provided HTMLElements, because that would be too expensive. Instead you get a lightweight React-internal description of an HTML element, of type React.Node. When you give this to React, it will perform magic to make the screen follow the description.

The simplest React Components one can define are just things with the same type as HTML tags, type Component<Props> = (Props) => React.Node. In fact, if you literally write a JavaScript function with this type, you get a React Component, what’s called a “functional” component. (Its name should start with a capital letter; this is how React distinguishes custom React Components from native HTML tags.) props is a JavaScript object keyed by attributes, including children for the children.

Also, React Components can can have state. Actually, some HTML tags have state too: for example, the text in an <input type="text" /> element is part of its state. To be useful, that text can’t simply be determined by an attribute provided by the creater of that input. So a more complex React component may be an ES6 class extending Component<Props, State> with a couple parts:

  • render, which is a function (Props, State) => HTMLElement. It’s not literally called as such; it’s a class method in which you access this.props and this.state. But it should still be pure in terms of those arguments. (Preact, a React alternative that lets go of browser compatibility to be extremely lean and fast, actually passes props and state as arguments to render in recognition of this.)
  • a constructor, which can set an initial value of state.
  • Optionally, various event listeners, including “lifecycle hooks”, which are methods called before or after when a React Component mounts or unmounts. A Component is “mounted” when first rendered to the DOM, and “unmounted” whenever it’s removed.

Event listeners can’t be pure, unfortunately, or else they wouldn’t be useful. They can read and write from a component’s state, by reading this.state or calling this.setState. Components can pass event handlers that set their own state as props to children.

As described, React is quite declarative. It just says how to render HTML. The magic of React that makes it fast is its “reconciliation” algorithm, which happens behind the scenes so that when a component only changes partially, React doesn’t have to fully rerender that component into the DOM; it will just keep the parts of the DOM that didn’t change as-is. Note that calling render on elements to create React.Nodes is much cheaper than rendering the results into the DOM and it looks like React will do that even if props and state don’t change, unless you tell it otherwise. Still, sometimes even render is expensive; one way to optimize React is to define shouldComponentUpdate, a function that says, given the next props and state, whether the component needs to be rerendered. You can make your components inherit from React.PureComponent instead of React.Component for the extremely common shouldComponentUpdate implementation that says false if the new props and state are shallowly equal to the old one. For this to work, you will need to avoid mutating data structures, but this is generally encouraged in React anyway, and hopefully if you’re programming functionally you were already doing that.

Also note that, as a heuristic, React never reconciles nodes produced by different components, even if they would ultimately result in the same markup. This has important ramifications: for example, you should try to avoid defining or creating new component types and handlers inside render(), so that you don’t define a different component with the same name every time you run render.

Syntax

React is typically used with the JSX syntax extension, which looks a lot like letting you write inline HTML in JavaScript. The inline HTML transpiles to a call to React.createElement, so it’s just another JavaScript expression.

const element = <div>Hi!</div>;

Inside HTML tags you can write text and other HTML tags. To break into JavaScript expressions to embed text or attributes, use curly braces. Text will get escaped when it’s rendered; you can’t inject HTML unless you try, with dangerouslySetInnerHTML. You can use JSX syntax in the JavaScript expression in curly braces in a JSX expression, and so on.

const text = "Hi!";
const element = <div id={"i" + "d"}>{text}</div>;

Note that JSX looks like HTML but does not try overly hard to be like it. Many attribute names like tabindex become camelCased like tabIndex, and a handful of HTML attributes are special. Most notably, className should be used instead of class. If an attribute “should” take a JavaScript boolean (e.g. checked or disabled) or a function (all event handlers), it does. The style attribute takes an object keyed by CSS attribute. Also, specifying an attribute without a value is the same as providing true for its value, e.g. <button disabled>Hi</button> is equivalent to <button disabled={true}>Hi</button>.

You use React Components (either functions or ES6 classes) in JSX with the same syntax as above. The name simply must be uppercase:

const element = <Hello foo="bar">baz</Hello>;

Your Hello component will get the object {foo: 'bar', children: 'baz'} as its props. (Technically I think children will not literally be a JavaScript string, but a React.Children thing containing it.)

Use the magic React Component <React.Fragment> to get an expression that contains some adjacent HTMLElements or text without a single element wrapping everything. For example:

const frag = <React.Fragment><div>a</div><div>b</div></React.Fragment>;
const wrapped = <div>{frag}</div>;

wrapped will just be a div with two divs inside it.

Philosophy, Escape Hatches, Beneath the Hood

React is declarative and pretty functional. It’s designed around downwards data flow: parents give their children data so the children can render correctly, and so on. There should be one true source of each piece of state. Often parents keep state and pass it to their children as props.

When you need to do more with the DOM, for example to cause imperative animations, you can use refs, which enable you to get the underlying DOM nodes or instances of your ES6 class component.

If you ever find a parent needing to pass data to far-removed descendants and don’t want to thread it through the props of a zillion children in between, you can use contexts.

A higher-order component is a function that takes a React component and returns a new React component. Preferably, the function is pure and the new component just composes something around the component. These should be called outside render as described above.

Redux

Redux is a predictable state container for JavaScript apps.

Redux is a framework that forces you to put all your mutable state in one place. The store stores the One True Copy of the state.

Redux and React are often mentioned in the same breath, but it is worth noting that React is a fine framework even without Redux and that Redux can also be used with non-React frameworks. The creators of Redux are very much on record saying that you don’t need to use Redux if you don’t feel the need.

Anyway, an action is a plain JavaScript object that describes a State => State function. You decide what actions exist and how they describe state transformations. However, there are conventions worth mentioning here. The most basic convention is that actions have a type attribute whose value is a string constant. The “Flux Standard Action” (FSA) specification is an even more rigid specification for an action’s type that is commonly adhered to. (Flux is the framework by Facebook that inspired Redux, by the way; Redux is stricter in its functional programming design.)

type FSA<Data> = {
  type: string,
  error?: boolean,
  payload?: Data | Error,
  meta?: any,
}

If error is true, then payload should be an error object. type, error, and payload should completely describe the state transformation; meta is for other metadata that might be useful for, say, debugging.

A reducer is just a function (Action, State) => State that turns an action into what it represents. An action creator is just a function that returns an action, because you don’t want to keep writing plain JavaScript objects manually; it never directly interacts with Redux.

The built-in combineReducers is a higher-order function that lifts a JavaScript object, whose values are functions of type (Action, innerState) => innerState, to a single (Action, OuterState) => OuterState function, where OuterState has a bunch of different fields with different types for innerState under keys corresponding to the original object. It is a convenience function that lets you write a big reducer conveniently in terms of a bunch of small reducers, nothing more.

Actions are dispatched by calling store.dispatch(action) (often indirectly) to modify the state. In some sense, dispatch: Action => IO ().

Thunks

I have already lied. An action is basically anything that can be dispatched. They don’t need to be plain JavaScript objects, but it is standard practice to make them so because it maximizes the debugging benefits of Redux.

However, you can make Redux support more kinds of actions by adding middleware, which are things that sit in the store and receive every action. Middleware can choose to intercept actions or let them pass through (to the next middleware), and can also act on the actions, say by dispatching other actions (although you must be careful not to cause an infinite loop here); only if the actions pass through all middleware does it hit the dispatchers. redux-thunk is a commonly added piece of middleware that intercepts any action that is a JavaScript function, which is called a thunk. redux-thunk intercepts and handles thunks by calling them with the functions dispatch and getState as arguments; thunks can do whatever they want with these functions. Note that thunks can’t (at least, shouldn’t) directly set state, and some people even discourage thunks that use getState. Generally, a thunk should just dispatch other actions that will then modify the state.

Thunks can in particular dispatch actions asynchronously, and they are the standard Redux way to do so. A thunk might, say, make an AJAX request with a callback that dispatches an action based on the result. However, nothing at all will break if you directly asynchronously dispatch an action in an event handler. Thunks are recommended only for reasons of encapsulation and ease of maintenance — this SO answer argues this, although to me it’s just wrong to call loadData an “action creator” when it does the asynchronous dispatch itself, as compared to the actually pure action creators in the example that fully use thunks. I am not sure and currently think that the rule of putting all asynchronous actions in thunks is useful on net, but is also one of the first rules of Redux I’d consider bending.

There are many other kinds of middleware. For instance, middleware can be useful for debugging, simply by letting every action through as-is but logging them. There are also other Redux libraries that provide ways for actions to dispatch other actions asynchronously, but it seems accepted that only actions that are simple JavaScript objects should directly modify state.

  • redux-promise is middleware that intercepts two kinds of actions:

    1. Promise<Action> (well, Promise<any>, and the any better be an action): it asynchronously dispatches the Action if the Promise resolves; it does nothing if the Promise fails.

    2. FSA<Promise<any>> (a FSA where the payload is a promise): if the Promise resolves, it dispatches the same FSA, but with the result instead of the Promise as the payload; if the Promise rejects, it dispatches the same FSA, but with the error instead of the Promise as the payload and error: true.

  • redux-promise-middleware also intercepts actions FSA<Promise<any>>. If the FSA’s type is FOO, it immediately dispatches an action of type FOO_PENDING (but no payload). Asynchronously, if the Promise resolves, it dispatches an action of type FOO_FULFILLED and payload equal to the result; if the Promise rejects, it dispatches an action of type FOO_REJECTED, payload equal to the error, and error: true.

  • redux-observable is sort-of-not-really middleware. To use it, you write “Epics”, which have type Observable<Action> => StateObservable<State> => Observable<Action>. They receive actions after the actions have already gone through the reducers.

    What is an Observable<T> anyway? A literal definition is that an Observable<T> is something that takes three listeners onNext : T => IO (), onError : Error => IO (), and onCompleted : () => IO (), and will call onNext zero or more times followed optionally by one final call to onError or onCompleted, but not both. It is contractually obligated not to do these things out of order.

    You can easily convert an array of T to an Observable<T> that immediately offers all elements of its array to any subscriber. Other examples of Observables include all clicks the user will make on a particular button. You can transform Observables by mapping (obviously Observable is a functor) but also by list operations like filtering, slicing, scanning, grouping, or accumulating, or with time-dependent transformations like debouncing or periodically sampling. You can also merge multiple Observables into one.

    It is tempting to think of Observables as lazy asynchronous lists whose elements arrive over time, and some Observables are like that, but it’s possible for observers/listeners to “miss” elements in Observables that are emitted before the observers are subscribed. This is the difference between “hot” and “cold” observables, but that’s its own can of worms.

    Some obligatory typeclass information:

    • There are several ways to make Observable<T> a monoid. The simplest is merge, an observable that calls the listener whenever either of its constituent observables emits an item. One might hope that merge would be commutative, but it’s possible for something to synchronously cause two Observables to emit something, in which case order matters.

    • Observable is a monad, but a really terrible one.

    Do note that Observables are, or should be, pure, which is why Epics only get to listen to the Actions and not intercept any. Any asynchrony happens during transforming the Actions to different Actions in the output.

  • redux-saga uses, uh, sagas. Sagas are JavaScript generator functions that yield objects that represent instructions. The Saga pattern is an existing thing and somebody somewhere wrote a post about how redux-saga doesn’t faithfully implement it, but their blog is down so too bad. I have not investigated Sagas very closely, but they are a solution.

React-Redux

To reiterate, as the Redux tutorial stresses, React and Redux are not tied. You can use React by itself or with other state management libraries, or Redux with other UI libraries.

Since Redux wants to hold all mutable state, in React-Redux we don’t want React Components to store any state. So instead all React elements just take props. (This is not strict. Some kinds of local or UI-only state may still be best handled in React; it’s perfectly fine to do so.)

To create a Component with (read or write) access to state, you write it normally as a component that takes some props, then wrap it with the magical React-Redux function connect to produce a new Component. In its simplest usage, connect takes a selector function : GlobalState => StateProps that takes the global Redux state, extracts some properties that are relevant to the Component, and injects them as props to the inner component. (This is in addition to any props provided by the caller.) To provide write access, connect grants the ability to dispatch actions by also injecting the dispatch function as a prop (named dispatch). For those who’d like to separate state from the component as much as possible, the second optional argument to connect is a function that maps dispatch to more props; there, you can write handlers that dispatch actions that are tailored directly to the inner Component’s needs. There are more optional arguments that I won’t go into.

Conclusion

React is a popular way to build web interfaces if you’re used to thinking functionally or declaratively. Redux helps you manage the state in React or in other frameworks even more tightly. That’s it. Now you can make cool modern interactive websites, including those Single-Page Apps that are all the rage these days.

Of course, while you’re doing that, don’t forget about performance, bandwidth, and accessibility. If React seems too heavyweight or you don’t need too much browser compatibility, you could consider Preact. And not every website needs to be interactive! Traditionally server-rendered HTML and static websites are still good.

(note: the commenting setup here is experimental and I may not check my comments often; if you want to tell me something instead of the world, email me!)