Learning React(2): Visual Components

In the last post, we went through the visual mockup, state planing; and built the minimum scaffolding for the redux app. We will work on the visual components in the post.

Material Design

Google’s material design is not only the guideline for the android platform, but adopted by more and more iOS apps, such as the SurveyMonkey app. Applying the material design to our web app will strength the conception of the native look-and-feel.

Material UI is a nice react UI component library to implement the material design, and has a rich set of components to meet our need. It depends on react-tap-event-plugin for the time being:

npm install --save material-ui react-tap-event-plugin

AwesomeBar

Recall the visual mockup, the AwesomeBar contains the following UI elements:

We can render the AwesomeBar with the AppBar, DropDownMenu from the Material UI:

<AppBar
  title={
    <DropDownMenu value={place} onChange={this.handlePlaceChange.bind(this)}>
      <MenuItem value={"lap_pool"} primaryText="Lap Pool" />
      <MenuItem value={"kids_zone"} primaryText="Kids Zone" />
    </DropDownMenu>
  }
  iconElementLeft={<IconButton><NavigationMenu /></IconButton>}
/>

It is worthy noting that the this scope change in the event handling: we need to explicitly bind the component itself to the this pointer.

TimeTable

react-big-calendar is probably the most versatile calendar component off the shelf though it is way over-qualified for our needs; thus I decide to build it from the scratch. The TimeTable can be further decomposed as:

Clearly, the 24-hour timeline can be modeled as a two-column table with appropriate padding. It is a little tricky to align an arbitrary event div to the table cell. Thanks to the relative/absolute CSS trick, we can align the event from 8:30am to 10:00am as:

<div id="wrapper" style="position: relative">
  <table id="timeline">... ...</table>
  <div style="position: absolute; top: 35.417%;  height: 6.25%>
    Imporant event (8:30am - 10:00am)
  </div>
</div>

The trick is to stretch the height of the wrapper with the timeline table, then calculate the relative position and height with the proportion of the occurrence and duration:

Make it Swipable

It is quite trivial to support the swipe gesture with the react-swipeable. Just wrap the TimeTable’s div element with Swipeable component as:

<Swipeable
  onSwipedLeft={nextDay}
  onSwipedRight={previousDay}>
  <div style={divStyle}>
  ... ...
</Swipeable>

It works, but with a caveat: no transition animation as visual feedback during the swipe gesture. We will explore react’s transition animation later.

Connect to the React

A typical redux App container may look like:

src/containers/App.js

import * as Actions from '../actions'
class App extends Component { ... ... }

function mapStateToProps(state) {
  return {
    place: state.place,
    date: state.date
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)

It looks boring in the first sight, but indeed many interesting things happen here, let’s discuss in more details:

The state is the source of the truth, but maybe not convenient to consume. The props is a view of the state to facilitate the visual rendering. Assume we want to render BigDate with a different color if the date is today, the business logic to determine whether the state date is today SHOULD be consolidated in mapStateToProps; otherwise, our abstraction is leaking.

The function bindActionCreators binds the store.dispatch to each action method, and mapDispatchToProps expose all the actions to the App’s props. It is generally a good practice to pass only the minimum required actions down to the components to avoid surprising state transition. In our case, only selectPlace action is passed to the AwesomeBar as the props.

The connect function dynamically declares a new react Component, Connect with the original App wrapped. The Connect implement various react callbacks, such as componentDidMount among others. In the componentDidMount, it subscribes the redux’s store to detect the state change, and trigger the redraw by invoking react’s setState method.

Update@Aug 25 My colleague Nate pointed out that for a deeply nested component, it is more convenient to inject the dispatch instead of binding actions. Then the component may reference the dispath in its props, and it can trigger the action by dispatching the action payload.

This approach will significantly reduce the boilerplate code to pass actions as props to the bottom. But on the other hand, it couples the react component with the redux store to prevent further reuse; and no type check for the action.

Feed the Data

The webpack pipeline supports json-loader to import json object just as regular javascript module. We just need to configure the webpack as:

webpack.config.dev.js

module.exports = {
  ... ...
  module: {
    loaders: [ { ... ... },
    {
      test: /\.json$/,
      loader: 'json'
    } ]
  }
}

Summary

Our web app is in a good shape for a MVP release, but we haven’t touch any traditional front end chore, such as stylesheet among others. Let’s talk about the visual appeal in the next post.