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 HTMLElement
s, 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 creator 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 accessthis.props
andthis.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 passesprops
andstate
as arguments torender
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.Node
s 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.
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.
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:
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:Promise<Action>
(well,Promise<any>
, and theany
better be an action): it asynchronously dispatches theAction
if thePromise
resolves; it does nothing if thePromise
fails.FSA<Promise<any>>
(a FSA where the payload is a promise): if thePromise
resolves, it dispatches the same FSA, but with the result instead of thePromise
as the payload; if thePromise
rejects, it dispatches the same FSA, but with the error instead of thePromise
as the payload anderror: true
.
redux-promise-middleware
also intercepts actionsFSA<Promise<any>>
. If the FSA’s type isFOO
, it immediately dispatches an action of typeFOO_PENDING
(but no payload). Asynchronously, if thePromise
resolves, it dispatches an action of typeFOO_FULFILLED
and payload equal to the result; if thePromise
rejects, it dispatches an action of typeFOO_REJECTED
, payload equal to the error, anderror: true
.redux-observable
is sort-of-not-really middleware. To use it, you write “Epics”, which have typeObservable<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 anObservable<T>
is something that takes three listenersonNext : T => IO ()
,onError : Error => IO ()
, andonCompleted : () => IO ()
, and will callonNext
zero or more times followed optionally by one final call toonError
oronCompleted
, but not both. It is contractually obligated not to do these things out of order.You can easily convert an array of
T
to anObservable<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 transformObservables
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 multipleObservables
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 ismerge
, an observable that calls the listener whenever either of its constituent observables emits an item. One might hope thatmerge
would be commutative, but it’s possible for something to synchronously cause twoObservables
to emit something, in which case order matters.Observable
is a monad, but a really terrible one.
Do note that
Observable
s 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 howredux-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.