Exercise 6: Solution

In notebook:
FrontEndMasters React
Created at:
2016-06-21
Updated:
2016-06-21
Tags:
libraries React JavaScript
  ..
var {
  Route,
  DefaultRoute,
  NotFoundRoute,
  RouteHandler,
  Link
} = Router;

var App = React.createClass({
  getInitialState: function () {
    return {
      contacts: ContactStore.getContacts(),
      loading: true
    };
  },

  componentWillMount: function () {
    ContactStore.init();
  },

  componentDidMount: function () {
    ContactStore.addChangeListener(this.updateContacts);
  },

  componentWillUnmount: function () {
    ContactStore.removeChangeListener(this.updateContacts);
  },

  updateContacts: function () {..},

  render: function () {
    var contacts = this.state.contacts.map(function (contact) {
      return <li key={contact.id}><Link to="contact" params={contact}>{contact.first}</Link></li>;
    });
    return (
      <div className="App">
        <div className="ContactList">
        // 3. +++ Add about link
          <Link to="about">About</Link>
          <Link to="new">New Contact</Link>
          <ul>
            {contacts}
          </ul>
          <Link to="/nothing-here">Invalid Link (not found)</Link>
        </div>
        <div className="Content">
          <RouteHandler/>
        </div>
      </div>
    );
  }
});

var Index = React.createClass({
  render: function () {
    return <h1>Address Book</h1>;
  }
});

var Contact = React.createClass({

  mixins: [ Router.Navigation, Router.State ],

  getStateFromStore: function (id) {..},

  getInitialState: function () {..},

  componentDidMount: function () {..},

  componentWillUnmount: function () {..,

  componentWillReceiveProps: function () {..},

  updateContact: function () {..},

  destroy: function () {..},

  render: function () {..}
});

var NewContact = React.createClass({

  mixins: [ Router.Navigation ],

  createContact: function (event) {.},

  render: function () {
    return (
      <form onSubmit={this.createContact}>
        ..
      </form>
    );
  }
});

var NotFound = React.createClass({..});


// 2. ++++ add the About handler
var About = React.createClass({
  render: function () {
    return <h2>About</h2>;
  }
});

var routes = (
  <Route handler={App}>
    <DefaultRoute handler={Index}/>
    // 1. ++++ add new Route
    // don't need path attribute
    <Route name="about" handler={About}/>
    <Route name="new" path="contact/new" handler={NewContact}/>
    <Route name="contact" path="contact/:id" handler={Contact}/>
    <NotFoundRoute handler={NotFound}/>
  </Route>
);

Router.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

Note: 

Links are accessible. Can be opened in a new tab. All shortcuts, tabbed navigation work. 

Animations with the router

Shows demo (05-router-animations on a reactconf repo)
https://github.com/ryanflorence/reactconf-2015-HYPE/tree/master/demos
Animates a bar chart when clicking on different months.

The months data is a simple js object. 
Gets the month (name) from the URL, gets the value from the month of the months data object, calculates a width based on this, sets the styles object. The styles object is CSS and has a transition property. 

Shows another demo, 06-itunes-style-interface. Can click on a big tiles of album artworks and the album tracklist appears below with animation. Uses the iTunes API. 
There are two routes active. One is animating away, while the other is sliding in. 

Code walkthrough:
https://github.com/ryanflorence/reactconf-2015-HYPE/blob/master/demos/06-itunes-style-interface/components/Albums.js
  var React = require('react')
var { Link, RouteHandler, Navigation } = require('react-router')
var Colors = require('../utils/Colors')
var Arrow = require('./Arrow')

var IMAGE_SIZE = 100
var IMAGE_MARGIN = 10

var Albums = React.createClass({

  mixins: [ Navigation ],

  statics: {..},

  getInitialState () {..},

  componentDidMount () {..},

  componentWillReceiveProps (newProps) {..},

  calcAlbumsPerRow () {..},

  calcRows () {..},

  renderAlbum (release) {..},

  renderRow (row, index) {
    var currentId = this.props.params.id
    var { lastChildId } = this.state
    var hasCurrent = row.filter(release => release.id === currentId).length > 0
    var hadCurrent = row.filter(release => release.id === lastChildId).length > 0
    var sameRow = hasCurrent && hadCurrent
    return (
      <div key={index}>
        {row.map(this.renderAlbum)}
        // animation happens here 
        // does it have to current one?
        {hasCurrent ? <RouteHandler params={this.props.params} /> : null}
        // if doesn't have the current one 
        // I'm rendering the same thing twice with different data 
        // The component has to stateless as much as possible
        {(hadCurrent && !sameRow) ? <RouteHandler params={{id: lastChildId}} old={true}/> : null}
      </div>
    )
  },

  render () {
    var releases = this.calcRows().map(this.renderRow)
    return <div>{releases}</div>
  }
})

module.exports = Albums