<div id="app"></div>
$mainText: #1c140d;
$mainBack: #f2e9e1;
$accent1: #eee;
$accent2: #cbe86b;
$accent3: darken($accent2, 25%);

$mainFont: 'Open Sans', sans-serif;

$smallBreak: 320px;
$medBreak: 480px;
$largeBreak: 720px;

body {
  font-family: $mainFont;
}

a {
  color: $mainText;
  text-decoration: none;
  text-align: center;
}

.hidden {
  top: -1000px;
  opacity: 0;
  position: fixed;
  z-index: -1;
  
  div, ul, li, a, img, h2, h3, input {
    display: inline-block;
    z-index: -10;
  }
  
  &.quick-alert {
    z-index: -1;
  }
  
  .form-inside {
    position: inline-block;
    margin-top: 8%;
  }
  
  .alert-inside {
    position: inline-block;
    margin-top: 8%;
  }
}

.box {
  background-color: lighten($mainBack, 80%);
  border: 1px solid darken($accent1, 10%);
  box-shadow: 2px 2px 2px darken($mainBack, 50%);
}

.btn {
  background-color: darken($accent2, 35%);
  border: 0px solid white;
  border-radius: 0.5em;
  color: $mainBack;
  cursor: pointer;
  font-size: 0.6em;
  letter-spacing: 0.1em;
  padding: 1% 1.5%;
  z-index: 1000;
    
  &:hover {
    background-color: saturate(darken($accent2, 40%), 20%);
  }
  
  &.close {
    background-color: rgba(0,0,0,0);
    color: $accent3;
    font-size: 1.1em;
    font-weight: 400;
    padding: 0;
    margin: 0;
  }
}

.btn-link {
  @extend .btn;
}

.header {
  align-items: center;
  background-color: $accent3;
  color: $mainBack;
  display: flex;
  flex-wrap: wrap;
  font-size: 1.8em;
  font-weight: 300;
  justify-content: space-between;
  letter-spacing: 0.1em;
  padding: 2%;
  
  box-shadow: 0px 2px 2px darken($mainBack, 50%);
  
  .head-title {
    margin: 0 0.5em;
    
    @media only screen and (max-width: $medBreak) {
      display: none;
      }
  }
  
  .btn-container {
    text-align: right;
    width: 50%;
    
    .btn {
      display: inline-block;
      margin-right: 1em;
      padding: 2%;
      vertical-align: middle;
      
      @media only screen and (max-width: $medBreak) {
        border-radius: 0.2em;
      }
      
      &:last-child {
        margin-right: 0;
      }
    }
  }
}

.title {
  text-align: center;
}

.nameList {
  margin-left: 1em;
  opacity: 1;
  padding: 2%;
  
  .nameLink {
    background-color: $accent2;
    border-radius: 0.5em;
    display: inline-block;
    margin-top: 1%;
    opacity: inherit;
    padding: 1%;
    
    &:hover {
      background-color: $accent3;
    }
  }
}

.recipe-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 1%;
  
  .recipe {
    padding: 1.5em;
    margin: 0.8em 0.5em;
  }
}

.fullsize-recipe-container { 
  
  &>li {
    @extend .box;
    display: flex;
    cursor: auto;
    justify-content: space-between;
    flex-direction: row;
    flex-wrap: nowrap;
    margin: 2% auto;
    padding: 3%;
    width: 90%;
    z-index: 21;
    
    transition: all 0.5s ease;
    
    @media only screen and (max-width: $medBreak) {
      flex-wrap: wrap;
    }
  }
  
  .pic-full {
    margin: 0 auto;
    min-width: 300px;
    width: 48%;
    
    @media only screen and (max-width: $medBreak) {
      margin-top: 1em; 
    }
    
    @media only screen and (max-width: $smallBreak) {
        min-width: 0;
        width: 300px;
      }

    img {
      max-width: 100%;
      width: 100%;
      
      @media only screen and (max-width: $smallBreak) {
        max-width: 280px;
      }
    }
  }
  
  .details-full {
    margin: 0 auto;
    min-width: 300px;
    width: 48%;
    
    @media only screen and (max-width: $smallBreak) {
        min-width: 0;
        width: 300px;
      }
    
    .title-full {
      margin-top: 1em;
    }
    
    .close {
      font-weight: 400;
      padding: 0;
      position: absolute;
      max-width: 2em;
      right: 50px;
      // top: 150px;
      
      @media only screen and (max-width: $medBreak) {
        right: 15px;
        // top: 60px; 
      }
    }
  }
  
  .edit-details-full {
    margin: 0 auto;
    min-width: 300px;
    padding: 0 1em;
    width: 60%;
    
    @media only screen and (max-width: $smallBreak) {
        min-width: 0;
        width: 300px;
      }
    
    .form-div {
      width: 100%;
    }
    
    .title-full {
      margin-top: 1em;
      width: 100%;
    }
    
    .new-inputs {
      margin-left: 1.5em;
      width: 100%;
      
      @media only screen and (max-width: $medBreak) {
        margin-left: 0;
      }
    }
    
    .close {
      font-weight: 400;
      padding: 0;
      position: absolute;
      max-width: 2em;
      right: 50px;
      // top: 150px;
      
      @media only screen and (max-width: $medBreak) {
        right: 15px;
        // top: 60px; 
      }
    }
    
    label {
      display: inline-block;
      letter-spacing: 0.1em;
      margin-top: 0.7em;
    }
    
    input {
      font-size: 1em;
      margin: 0.5em;
      padding: 1%;
      width: 90%;
    }
  }
  
  .title-full {
    color: saturate(darken($accent3, 10%), 20%);
    font-size: 2em;
    margin-bottom: 0.7em;
  }
  
  .link.btn {
    display: inline-block;
    font-size: 1em;
    padding: 2%;
  }
  
  .link-row {
    display: flex;
    justify-content: center;
    margin-bottom: 0.5em;
    padding: 0;
    width: 100%;
    
    .btn {
      display: inline-block;
      flex-grow: 1;
      font-size: 1em;
      line-height: 1.5em;
      margin: 0;
      margin-right: 0.5em;
      padding: 2%;
      
      @media only screen and (max-width: $smallBreak) {
        padding: 1%;
      }
      
      &:last-child {
        margin: 0;
      }
    }
  }
  
  .ingredients-full {
    background-color: lighten($accent2, 15%);
    font-size: 1.1em;
    padding: 4%;
    
    h3 {
      font-size: 1.2em;
      letter-spacing: 0.1em;
      margin-bottom: 0.5em;
    }
    
    ul {
      // overflow-y: scroll;
    }
    
    li {
      list-style-type: circle;
      margin-bottom: 0.4em;
      margin-left: 1.5em;
      
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
}

.recipe {
  @extend .box;
  cursor: pointer;
  height: auto;
  width: 15em;
  z-index: 20;
  
  img {
    width: 100%;
  }
  
  .title {
    margin: 1em 0 0 0;
  }
}

.add-form {
  background-color: rgba(0,0,0,0.25);
  display: flex;
  height: 100vh;
  overflow: hidden;
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 1050;
  
  &.hidden {
    z-index: -1;
    transition: all 0s ease;
    
    .form-inside {
      margin-top: 8%;
    }
  }
  
  transition: opacity 0.5s ease;
  
  .form-inside {
    background-color: lighten($mainBack, 80%);
    border: 1px solid $accent1;
    border-radius: 0.5em;
    display: inline-block;
    height: 14em;
    margin: 5% auto;
    padding: 2%;
    position: relative;
    width: 400px;
    
    transition: margin 0.5s linear;
    
    input {
      margin: 1%;
      padding: 1%;
      width: 96%;
    }
    
    .btn {
      color: $mainBack;
      font-size: 1em;
      margin-top: 2%;
      margin-left: 87%;
      text-align: center;
      width: 10%;
    }
    
    h2 {
      font-size: 1.3em;
      font-weight: 300;
      letter-spacing: 0.1em;
      margin-bottom: 5%;
      
      div {
        display: inline-block;
        width: 95%;
      }
      
      .close {
        color: $accent3;
        margin: 0;
        text-align: center;
        width: 0.8em;
      }
    }
  }
}

.quick-alert {
  background-color: rgba(0,0,0,0.25);
  display: flex;
  height: 100vh;
  overflow: hidden;
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 1051;
  
  transition: all 0.5s ease;
  
  .alert-inside {
    background-color: lighten($mainBack, 80%);
    border-radius: 0.5em;
    display: inline-block;
    font-size: 1.1em;
    height: 1em;
    margin: 5% auto;
    padding: 3%;
    position: relative;
    text-align: center;
    width: 30%;
    
    transition: margin 1s linear;
    
    @media only screen and (max-width: $medBreak) {
      width: 60%;
    }
  }
}
View Compiled
const Router = ReactRouter.Router;
const Route = ReactRouter.Route;
const IndexRoute = ReactRouter.IndexRoute;
const Link = ReactRouter.Link;
const hashHistory = ReactRouter.hashHistory;

let initialRecipes = [
  {
          name: 'Loaded Guacamole Tacos',
          nameLink: 'LoadedGuacamoleTacos',
          img: 'http://images.soupaddict.com/loaded-guacamole-vegetarian-tacos-3-062214.jpg',
          ingredients: [
            'fresh avocados',
            'black beans',
            'jalapenos',
            'tomatoes or tomatillos',
            'corn or small flour tortillas',
            'corn',
            'lime',
            'cilantro'
          ],
          source: 'http://soupaddict.com/2014/06/loaded-guacamole-vegetarian-tacos/'
        },
  {
          name: 'Green Curry',
          nameLink: 'GreenCurry',
          ingredients: [
            'coconut milk',
            'carrots',
            'onions',
            'garlic',
            'green curry paste',
            'asparagus',
            'cilantro'
          ],
          img: 'http://cookieandkate.com/images/2015/03/thai-green-curry-recipe-0.jpg',
          source: 'http://cookieandkate.com/2015/thai-green-curry-with-spring-vegetables/'
        },
  {
          name: 'Raspberry Chocolate Tart',
          nameLink: 'RaspberryChocolateTart',
          ingredients: [
            'raspberry preserves',
            'cocoa powder',
            'fresh raspberries',
            'coconut milk',
            'almond flour'
          ],
          img: 'http://www.bakerita.com/wp-content/uploads/2015/06/No-Bake-Raspberry-Chocolate-Truffle-Tart-Paleo-11.jpg',
          source: 'http://www.bakerita.com/no-bake-raspberry-chocolate-tart-paleo-vegan-gf/'
        }
];

if (window.localStorage) {
  if (!window.localStorage.recipeBox) {
    window.localStorage.setItem('recipeBox', JSON.stringify(initialRecipes));
  }
}

let recipeStore = JSON.parse(window.localStorage.getItem('recipeBox'));

const RecipeRouter = React.createClass({
  getInitialState: function() {
    return {
      formClass: 'hidden',
      recipes: recipeStore,
      alertMsg: 'I\'m an alert!',
      alertMsgClass: 'hidden'
    }
  },
  showForm: function(e) {
    e.preventDefault();
    this.setState({
      formClass: ''
    })
  },
  closeForm: function(e) {
    this.setState({
      formClass: 'hidden'
    });
    e.preventDefault();
  },
  alertMessage: function(msg) {
    this.setState({
      alertMsg: msg,
      alertMsgClass: ''
    })
    
    setTimeout(() => {
      this.setState({
        alertMsgClass: 'hidden'
      });
    }, 1500);
  },
  addRecipe: function(e) {
    e.preventDefault();
    let newName = document.getElementById('new-title').value;
    let newSource = document.getElementById('new-source').value;
    let newImg = document.getElementById('new-img').value;
    let newIngredients = document.getElementById('new-ingredients').value;
    
    if (!newName || !newSource || !newIngredients) {
      this.setState({
        formClass: 'hidden'
      });
      return;
    }
    
    newIngredients = newIngredients.split(',');
    
    let newRecipe = {
      name: newName,
      nameLink: newName.replace(' ', ''),
      source: newSource,
      img: newImg,
      ingredients: newIngredients
    }
    
    document.getElementById('new-title').value = "";
    document.getElementById('new-source').value = "";
    document.getElementById('new-img').value = "";
    document.getElementById('new-ingredients').value = "";
    
    let newList = [];
    this.state.recipes.forEach(function(recipe) {
      newList.push(recipe);
    });
    
    newList.push(newRecipe);
    
    window.localStorage.setItem('recipeBox', JSON.stringify(newList))
    
    this.setState({
      formClass: 'hidden',
      recipes: newList
    });
    
    this.alertMessage('Recipe added!');
  },
  deleteRecipe: function() {
    // find which recipe isn't hidden and save its recipeName    
    let recipeContainer = document.getElementsByClassName('fullsize-recipe-container')[0];
    let fullRecipes = recipeContainer.children;
    let recipeName = "";
    let removeIndex;
    
    Array.prototype.forEach.call(fullRecipes, function(recipe, index) {
      if (recipe.className !== 'hidden') {
        // below check only for deleting the read-only recipe
        if (recipe.id.indexOf('completeRecipe') > -1) {
          recipeName = recipe.id.replace('completeRecipe','');
          removeIndex = index;
        }
      }
    });
    
    let newList = [];
    
    newList = this.state.recipes.filter(function(recipe, index) {
      return index !== removeIndex;
    });
    
    window.localStorage.setItem('recipeBox', JSON.stringify(newList));
    
    this.setState({
      recipes: newList
    });
    
    this.alertMessage('Recipe deleted!');
  },
  editRecipe: function() {
    let recipeContainer = document.getElementsByClassName('fullsize-recipe-container')[0];
    let fullRecipes = recipeContainer.children;
    let recipeName = "";
    let editIndex;
    
    Array.prototype.forEach.call(fullRecipes, function(recipe) {
      if (recipe.className !== 'hidden') {
        recipeName = recipe.id.replace('editableRecipe','');
      }
    });
    
    for (var i=0; i<this.state.recipes.length; i++) {
      if (this.state.recipes[i].name === recipeName) {
        editIndex = i;
      }
    }
    
    //let changeRecipe = this.state.recipes[editIndex];
    
    let changeName = document.getElementById(`change-name${recipeName}`).value;
    let changeSource = document.getElementById(`change-source${recipeName}`).value;
    let changeImg = document.getElementById(`change-img${recipeName}`).value;
    let changeIngredients = document.getElementById(`change-ingredients${recipeName}`).value;
    
    let newNameLink = changeName.replace(' ', '');
    
    let editedRecipe = {
      name: changeName,
      nameLink: newNameLink,
      ingredients: changeIngredients.split(','),
      img: changeImg,
      source: changeSource
    }
    
    let newRecipes = [];
    
    for (let j=0; j<this.state.recipes.length; j++) {
      if (j !== editIndex) {
        newRecipes.push(this.state.recipes[j])
      }
      else {
        newRecipes.push(editedRecipe);
      }
    }
    
    window.localStorage.setItem('recipeBox', JSON.stringify(newRecipes));
    
    this.setState({
      recipes: newRecipes
    })
    
    this.alertMessage('Changes saved!');
  },
  createElement: function(Component, props) {
    return <Component recipes={this.state.recipes} delete={this.deleteRecipe} edit={this.editRecipe} {...props} />
  },
  render: function() {
    return (
      <div>
        <Router createElement={this.createElement} history={hashHistory}>
          <Route path='/' component={wrapComponent(TopBar, {showForm: this.showForm})}>
            <IndexRoute component={RecipeThumbs} />
            <Route path='/recipeList' component={RecipeList} />
            <Route path='/recipeFull/:recipeName' component={RecipeFullList} />
          </Route>
        </Router>
        <Form submitEvent={this.addRecipe} isShown={this.state.formClass} closeEvent={this.closeForm} />
        <QuickAlert isShown={this.state.alertMsgClass} message={this.state.alertMsg} />
      </div>
    )
  }
});

const wrapComponent = function(Component, props) {
  return React.createClass({
    render: function() {
      return (
        React.createElement(Component, props, this.props.children)
      );
    }
  });
};

const TopBar = React.createClass({
  getInitialState: function() {
    return {
      iconName: 'fa fa-list-ul'
    }
  },
  toggleViews: function() {
    var newIcon = this.state.iconName === 'fa fa-th' ? 'fa fa-list-ul' : 'fa fa-th';
    this.setState({
      iconName: newIcon
    })
  },
  render: function() {
    let otherView = this.state.iconName === 'fa fa-list-ul' ? '/recipeList' : '/';
    return (
      <div>
        <div className="header">
          <div>
            <i className="fa fa-cutlery"></i>
            <span className="head-title">recipe box</span>
            <i className="fa fa-spoon"></i>
          </div>
          
          <div className='btn-container'>
            <Link className='btn-link' to={otherView} onClick={this.toggleViews}>
              <i id="switch-view-icon" className={this.state.iconName}></i>
            </Link>
            <Button id="add-btn" type="button" text="add recipe" clickEvent={this.props.showForm} />
          </div>
        </div>
        {this.props.children}
      </div>
    )
  }
});

function QuickAlert(props) {
  let currClass = `${props.isShown} quick-alert`;
  return (
    <div className={currClass}>
      <div className="alert-inside">
        {props.message}
      </div>
    </div>
  )
}

const RecipeThumbs = React.createClass({
  render: function() {
    let thumbList = this.props.recipes.map(function(recipe, index) {
      let key = `thumb${index}`    
      return (
        <div className="recipe" key={key}>
          <Recipe data={recipe} />
        </div>
      )
    }.bind(this));
    
    return (
      <ul className='recipe-container'>
        {thumbList}
      </ul>
    )
  }
});

function RecipeList(props) {
  let nameList = props.recipes.map(function(recipe, index) {
    let redirect = `/recipeFull/${recipe.nameLink}`
    let key = `nameOnly${index}`;
    return (
      <li key={key}>
        <Link className="nameLink" to={redirect}>
          {recipe.name}
        </Link>
      </li>
    )
  })
  
  return (
    <ul className="nameList">
      {nameList}
    </ul>
  )
}

function Recipe(props) {
  let redirect = `recipeFull/${props.data.nameLink}`;
    return (
      <li>
        <Link to={redirect}>
          <div className="pic">
            <img src={props.data.img} alt={props.data.name} />
          </div>
          <div className="title">{props.data.name}</div>
        </Link>
      </li>
    )
}

const RecipeFullList = React.createClass({
  contextTypes: {
    router: React.PropTypes.object,
    location: React.PropTypes.object
  },
  render: function() {    
    var recipeName = this.context.location.pathname.replace('/recipeFull/', '').replace('%20', ' ');
    let fullList = this.props.recipes.map(function(recipe, index) {
      let keyFull = `recipeFull${index}`;
      let showClass = recipeName === recipe.nameLink ? '' : 'hidden';
      
      return (
          <RecipeFull delete={this.props.delete} close={this.context.router.goBack} key={keyFull} data={recipe} show={showClass} edit={this.props.edit} />
        )
      }.bind(this));
    
    let editList = this.props.recipes.map(function(recipe, index) {
      let keyFull = `editFull${index}`;
      let linkString = `edit${recipe.nameLink}`;
      let showClass = recipeName === linkString ? '' : 'hidden';
      
      return (
          <EditableRecipeFull close={this.context.router.goBack} key={keyFull} data={recipe} show={showClass} edit={this.props.edit} />
        )
      }.bind(this));
    
    return (
      <ul className="fullsize-recipe-container">
        {fullList}
        {editList}
      </ul>
    )
  }
});

const RecipeFull = React.createClass({
  contextTypes: {
    router: React.PropTypes.object,
    location: React.PropTypes.object
  },
  render: function() {
    let id = `completeRecipe${this.props.data.name}`;
    let editLink =  `/recipeFull/edit${this.props.data.nameLink}`;
    let returnView = document.getElementById('switch-view-icon').className;
    let backLink = returnView === 'fa fa-list-ul' ? '/' : 'recipeList/';
    
    return (
      <li className={this.props.show} id={id}>
        <a href={this.props.data.source} target="_blank" className="pic-full">
          <img src={this.props.data.img} alt={this.props.data.name} />
        </a>
        <div className="details-full">
          <LinkButton btnClass='close' redirect={backLink} text="X"/>
          <div className="title-full title">{this.props.data.name}</div>
          <div className="link-row">
            <a href={this.props.data.source} target="_blank" className="link btn">source</a>
            <LinkButton text="edit" redirect={editLink} />
            <LinkButton clickEvent={this.props.delete} redirect='/' text="delete" />
          </div>
          <IngredientsList recipe={this.props.data.name} ingredients={this.props.data.ingredients} />
        </div>
      </li>
    )
  }
});

const EditableRecipeFull = React.createClass({
  getInitialState: function() {
    // this doesn't work unless there's a change. 'x' button doesn't go back
    return {
      redirect: ''
    }
  },
  nameChange: function() {
    let newName = document.getElementById(`change-name${this.props.data.name}`).value.replace(' ', '');
    
    this.setState({
      redirect: `recipeFull/${newName}`
    }, function() {console.log(this.state.redirect)})
    
  },
  contextTypes: {
    router: React.PropTypes.object,
    location: React.PropTypes.object
  },
  render: function() {
    let id = `editableRecipe${this.props.data.name}`;
    let changeName = `change-name${this.props.data.name}`;
    let changeIng = `change-ingredients${this.props.data.name}`;
    let changeImg = `change-img${this.props.data.name}`;
    let changeSource = `change-source${this.props.data.name}`;
    
    let goBack = this.state.redirect === '' ? `recipeFull/${this.props.data.nameLink}` : this.state.redirect;
    
    return (
      <li className={this.props.show} id={id}>
        <a href={this.props.data.source} target="_blank" className="pic-full">
          <img src={this.props.data.img} alt={this.props.data.name} />
        </a>
        
        <div className="edit-details-full">
          <LinkButton redirect={goBack} text="X" btnClass='close' />
          <div className="title-full title">Edit {this.props.data.name}</div>
          <div className='form-div'>
            <div className="new-inputs">
              <label htmlFor='change-name' className="change-subtitle">New Name</label>
              <input id={changeName} name='change-name' onChange={this.nameChange} defaultValue={this.props.data.name} />
            
              <label htmlFor='change-ingredients' className="change-subtitle">New Ingredients</label>
              <input name='change-ingredients' id={changeIng} defaultValue={this.props.data.ingredients.toString()} />
            
              <label htmlFor='change-img' className="change-subtitle">New Image Url</label>
              <input name='change-img' id={changeImg} defaultValue={this.props.data.img} />
              
              <label htmlFor='change-source' className="change-subtitle">New Recipe Source</label>
              <input name='change-source' id={changeSource} defaultValue={this.props.data.source} />
              <LinkButton btnClass='link' clickEvent={this.props.edit} redirect={goBack} text="submit changes" />
            </div>
          </div>
        </div>
      </li>
    )
  }
});

function IngredientsList(props) {
  let ingList = props.ingredients.map(function(item, index) {
    let key = `${props.recipe}Ingredient${index}`;
    return (
      <li key={key}>{item}</li>
    )
  });
  return (
    <div className="ingredients-full">
      <h3>Ingredients</h3>
      <ul>
        {ingList}
      </ul>
    </div>
  )
}

const Form = React.createClass({
  render: function() {
    let formClass = `add-form ${this.props.isShown}`;
    return (
      <form onSubmit={this.props.submitEvent} id="recipe-form" className={formClass}>
        <div className="form-inside">
          <h2>
            <div>add new recipe</div>
            <Button text="x" class="close btn" type="button" clickEvent={this.props.closeEvent} />
          </h2>
          <input id="new-title" type="text" placeholder="recipe title"/>
          <input id="new-source" type="text" placeholder="recipe source" />
          <input id="new-img" type="text" placeholder="recipe picture" />
          <input id="new-ingredients" type="text" placeholder="enter ingredients, separated by a comma" />
          <Button clickEvent={this.submit} type="submit" text="add" />
        </div>
      </form>
    )
  }
});

function Button(props) {
  // props.type should only be 'submit' or 'button'
  let newClass = props.class ? props.class : 'btn';  
  return (
      <input type={props.type} className={newClass} onClick={props.clickEvent} value={props.text} />
  )
}

const LinkButton = React.createClass({
  getDefaultProps: function() {
    clickEvent: () => {}
  },
  render: function() {
    let newClass = this.props.btnClass ? `btn ${this.props.btnClass}` : 'btn';
    return (
      <Link className={newClass} onClick={this.props.clickEvent} to={this.props.redirect}>{this.props.text}</Link>
    )
  }
})

ReactDOM.render(
  <RecipeRouter />,
  document.getElementById('app')
)
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://fb.me/react-0.14.2.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-router/2.0.1/ReactRouter.js
  3. https://fb.me/react-dom-0.14.2.js