Learning React(2): Visual Components
reactIn 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:
- An AppBar
- A drop down menu to select the place
- A system menu used in the future
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:
- current date and the day of the week, called BigDate
- the 24-hour timeline
- the events
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:
8.5 / 24 = 0.35417
1.5 / 24 = 0.0625
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.