React-Redux

I’ve been trying to get a clear picture of the data flows in React-Redux, and I thought I’d share that mental flowering. I’m still quite new with React-Redux so I’d suggest reading it with a note of caution but knowing that the end result was working software.

Redux

In Redux itself the data flow seems relatively straight-forward:
1. When something happens we dispatch an action to the store i.e. store.dispatch({ type: "TYPE_KEY", ... }).
2. Redux then calls the reducer which returns a new state back to Redux.
3. Functions that were previously registered using store.subscribe(fn) are called and those functions call store.getState() to get the new state.
This seems like a fairly classic observer pattern around an event-store.

If I were translating this to React then I’d expect that within my component I’d register a listener against the store that would call this.setState({ ... }). Then setState() would trigger the framework to re-render that component. The problem with this plan and react-redux is subscribe() is nowhere to be seen. Nor for that matter is store – so where did they go?

The answer is in the connect() method.

Connect()

When connect() is called on a component used inside a <Provider> tag, connect() automatically picks up the Provider’s store. This store is configured at the highest level of the application, something like this:

import { createStore } from "redux";
import { Provider } from "react-redux";
import reducer from "./state/reducers";

let store = createStore(reducer); // reducer is the outcome of combineReducers()

ReactDOM.render(
    <Provider store={store}>
        <Router />
    </Provider>,
    document.getElementById("example")
);

Because connect() encapsulates the store, it needs to provide an interface to allow the component to (implicitly) access the store. It does this via two arguments, which the documentation calls mapStateToProps and mapDispatchToProps.

mapStateToProps

mapStateToProps is effectively a filter that selects the bits of the global state from the store that are of interest to the component. The bits of state returned from this method are added to the this.props object.

This was the slightly counter-intuitive part in migrating from storing the data in React state. React documentation indicates that the data owned by a component should be stored in this.state. When it’s transferred to the store, that component is no longer strictly the owner and shouldn’t be changing it without informing the store, so it makes sense for it to be read-only, and therefore it moves to this.props.

mapStateToProps is the replacement for store.subscribe(). With it in place, any updates made to the store are filtered by the object returned from this method, and are then applied to the component’s this.props, causing the standard component updating lifecycle including the render.

mapDispatchToProps

mapDispatchToProps allows us to access the store.dispatch(). It provides the dispatch function as an argument, and should return an object containing methods that call dispatch with various actions. e.g.

const mapActionToProps = (dispatch) => {
    return {
        requestUsers: () => dispatch({ type: "REQUEST_USERS" }),
        updateUser: (user) => dispatch({ type: "UPDATE_USER", user: user })
    }
}

This enables the component to trigger actions by calling this.props.methodname(args).

connect(mapStateToProps, mapDispatchToProps) returns a function which should be passed the class or function of the component using the store. Convention also has that class returned as the default export so it can be directly imported. e.g.

// in component file
export default connect(mapStateToProps, mapActionToProps)(ComponentName)

// in file that is using the component
import ComponentName from "./ComponentName"

React-Redux Cycle

Putting it altogether we get a data flow something like this:
1. When something happens we dispatch an action to the store by calling an action property. i.e. this.props.requestUsers().
2. This calls dispatch(action-object) inside the method used in step 1, whose definition is found in mapActionToProps.
3. Redux then calls the reducer which returns a new state back to Redux.
4. The component receives the new state, filters it according to mapStateToProps and applies the filtered object to this.props triggering the normal React component update lifecycle and render.

Starting React

I’ve been dabbling with the concept of a remote part-time job to provide a bit of personal funding as our business venture slowly gets off the ground, and this has highlighted the current popularity of React. It’s not like I’ve had my head completely in the sand, but the number of roles requiring a specific JS framework, rather than just JavaScript (plus a framework preference) did surprise me a little.

With all that in mind I decided to take a proper look at React, and compare it to my current front-end framework of choice, Aurelia. The fruits of this exploration are in GitHub and hopefully not too embarrassing :).

Philosophy

There is a continuum in current web libraries between being a full framework, like Angular or Aurelia, and a view library, where non-rendering activities like routing and dependency injection are not included. React sits at the view library end of this spectrum, but with well established and supported choices for the excluded activities. In this respect I’ve found React easier to learn than Aurelia because it enables the ecosystem to be learned step-at-a-time – although it could also be said that significant experience with any of these frameworks would make learning the next easier.

React is strong on conventions, although at times it has felt like these conventions obscure what is going on and make ‘magic happen’, which is not a feeling I enjoy when developing. This is particularly so with react-redux.

It also leans more towards JavaScript’s dynamic typing than a static typing style, emphasized in the event handler example which does this.setState({ [name]: value });. However it also encourages the use of typed systems like TypeScript which requires some trawling through the index.d.ts file to come to grips with.

Components

React is driven by the notion of small and composable components, and it does make creating and working with these very easy. The use of JSX/TSX files allows advanced logic to be mixed with markup giving complete flexibility over the creation of elements. There is a risk to this, which is that a component gets bloated instead of being split into smaller parts, so good code maintenance practices are important.

Styling is fairly flexible, allowing anything from basic CSS use, referenced from the index.html, through to compiled and bundled SCSS using a packing system like webpack.

Data

My basic understanding is that data that is owned by a component goes into this.state and is modified by setState(), while data that is used by a component goes into this.props and is read-only. In a basic scenario (i.e. without Redux) data flows down via properties on this.props, and up via callback methods on this.props. e.g.

// in parent component
private handleChange(data) {
  this.setState({ filtered: data});
}

render() {
  <Subcomponent all={this.all} onChange={(data) => this.handleChange(data)} />
}

// in a subcomponent method
this.props.onChange(this.props.all.filter(f => f.something > 0));

This is certainly more laborious than Aurelia’s two-way binding, something React’s documentation acknowledges: “It can sometimes be tedious to use controlled components, because you need to write an event handler for every way your data can change and pipe all of the input state through a React component.” However this approach does make data passing and ownership very explicit and therefore understandable. Going to react-redux changes all this, and that’s something I’ll leave for a future post.

So there you have it – my first couple of days building something with React.

Using aurelia-ux v0.6

aurelia-ux is the pre-release UI component kit for aurelia. It has been developed throughout 2017 including a significant re-organization between v0.3 and v0.4. Due to this reorganization and its pre-release status, there is limited information out there on how to use it, with the best example at present being aurelia-store-todo. There is an indication that documentation is on the way and hopefully as part of that the team’s app-ux-showcase will be updated past v0.3. In the meantime I decided to dig into the source a bit and build a little something to share here.

Setup

This setup assumes you have aurelia-cli running, so that you can go to a command line and type au new to create the project. Open the project folder and add the following dependencies to the package.json then run npm install (or the yarn equivalent) to get the aurelia-ux packages into your node_modules folder:

  "@aurelia-ux/components": "^0.6.0",
  "@aurelia-ux/core": "^0.6.0",

aurelia-store-todo uses WebPack as the bundler, however I will be sticking to the aurelia-cli. This means editing the aurelia.json to include the dependencies. The first dependency is @aurelia-ux/core which contains the core capabilities. Then each component needs to be added individually. There is also an @aurelia-ux/components which may be intended to include all components, but including this causes build errors as the require exports for all the referenced components are not able to be resolved. The additions to the vendor-bundle dependencies in aurelia.json look something like this:

{
  "name": "@aurelia-ux/core",
  "path": "../node_modules/@aurelia-ux/core/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/input",
  "path": "../node_modules/@aurelia-ux/input/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/button",
  "path": "../node_modules/@aurelia-ux/button/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/form",
  "path": "../node_modules/@aurelia-ux/form/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
}

Using Components

The components are added as plugins to main.ts, like so:

aurelia.use
    .plugin(PLATFORM.moduleName('@aurelia-ux/core'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/input'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/button'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/form'));

This makes them available everywhere, without needing further require tags.

Each component has a tag and properties which can be used in a template file. For instance the button is <ux-button> and can be bound to a view model variable using value.bind e.g. <ux-input value.bind="rows"></ux-input>. The elements appear unmodified in the output HTML and can be styled using CSS. Here is an example of using three components in a view model.
app.html:

<template>
  <require from="./app.css"></require>
  <ux-form>
    Rows:<ux-input value.bind="rows"></ux-input>
    Columns: <ux-input value.bind="cols"></ux-input>
    <ux-button type="raised" click.trigger="generate()">Generate</ux-button>
  </ux-form>
</template>

app.ts:

export class App {
    protected rows: number = 5; // arbitrary default
    protected cols: number = 5; // arbitrary default
    protected generate() { ... }
}

app.css (you may need to create this):

ux-form {
    padding: 2px;
    max-width: 300px;
    border: 1px solid transparent;
    border-color: var(--ux-design--primary-light)
}

CSS Variables

You may have noticed in the CSS above the use of a variable: var(--ux-design--primary-light). This is a mechanism for globally styling aurelia-ux, and is setup in the app constructor as follows:

import { autoinject } from 'aurelia-framework';
import { AureliaUX } from '@aurelia-ux/core';

@autoinject
export class App {
    protected rows: number = 5; // arbitrary default
    protected cols: number = 5; // arbitrary default

    // ux.design members: 
    //     appBackground, appForeground, controlBackground, controlForeground, 
    //     primary, primaryForeground, primaryLight, primaryLightForeground, primaryDark, primaryDarkForeground, 
    //     accent, accentForeground, accentLight, accentLightForeground, accentDark, accentDarkForeground
    constructor(private ux: AureliaUX) {
        ux.design.primary = '#DD0088';
        ux.design.accent = '#CC88FF';
        // unfortunately we don't seem to be able to change the tints by just setting the base color, so set them as well
        ux.design.primaryLight = '#DB9BBF';
        ux.design.accentLight = '#E7C9FF';
    }

    protected generate() { ... }
}

The name of the CSS variable is --ux-design--<property-name> where the property name is one of the ux.design members above. You can see them all in your browser’s CSS viewer:
CSS Design Variables
It is worth noting that CSS variables do not work in IE11 so if you expect to support that browser then aurelia-ux may not be suitable for you.

More

This is as far as I’ve gone with aurelia-ux so far, but there’s certainly more out there.

There are 11 components as it stands: button; checkbox; chip-input (a tag list); datepicker; form; input; input-info; list; radio; textarea; and switch. To find out what properties they expose download and run app-ux-showcase, or open your node_modules/@aurelia-ux/<name>/dist/amd/ux-<name>.d.ts and look at the public properties and methods.

There is also a themeing system which enables you to define your own component themes by extending implementations of the UxTheme class. Any properties in those classes can be used as CSS variables. To fully grasp this at present I’d suggest downloading the aurelia-ux source.