├── .gitignore ├── README.md ├── exercises ├── __build__ │ └── shared.js ├── exercise01 │ ├── index.html │ └── index.js ├── exercise02 │ ├── index.html │ └── index.js ├── exercise03 │ ├── index.html │ └── index.js ├── exercise04 │ ├── index.html │ └── index.js ├── exercise05 │ ├── index.html │ └── index.js ├── exercise06 │ ├── index.html │ └── index.js └── exercise07 │ ├── index.html │ └── index.js ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-workshop 2 | 3 | Exercises as part of a React workshop 4 | 5 | ## Getting started 6 | 7 | ```bash 8 | $ git clone https://github.com/mzabriskie/react-workshop.git 9 | $ cd react-workshop 10 | $ npm install 11 | $ npm start 12 | $ open http://127.0.0.1:8080 13 | ``` 14 | 15 | See the excercises live here: 16 | 17 | - [Exercise 1 - Components](https://mzabriskie.github.io/react-workshop/exercises/exercise01/) 18 | - [Exercise 2 - Props](https://mzabriskie.github.io/react-workshop/exercises/exercise02/) 19 | - [Exercise 3 - Styles](https://mzabriskie.github.io/react-workshop/exercises/exercise03/) 20 | - [Exercise 4 - Composing Components](https://mzabriskie.github.io/react-workshop/exercises/exercise04/) 21 | - [Exercise 5 - State](https://mzabriskie.github.io/react-workshop/exercises/exercise05/) 22 | - [Exercise 6 - Fetching Data](https://mzabriskie.github.io/react-workshop/exercises/exercise06/) 23 | - [Exercise 7 - React Router](https://mzabriskie.github.io/react-workshop/exercises/exercise07/) 24 | 25 | ## License 26 | 27 | MIT 28 | -------------------------------------------------------------------------------- /exercises/__build__/shared.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = "/__build__"; 38 | /******/ }) 39 | /************************************************************************/ 40 | /******/ ([]); -------------------------------------------------------------------------------- /exercises/exercise01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 1 - Components 6 | 7 | 8 |

Exercise 1 - Components

9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercises/exercise01/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | /** 5 | * Everything in React starts with a component. A component can be created by 6 | * using `React.createElement`. The result of this can then be rendered using 7 | * `ReactDOM.render`. 8 | * 9 | * Example: 10 | * 11 | * ``` 12 | * ReactDOM.render( 13 | * React.createElement('div', null, 'This is my component'), 14 | * document.getElementById('container') 15 | * ); 16 | * ``` 17 | * 18 | * This is similar to the following code in JavaScript: 19 | * 20 | * ``` 21 | * let component = document.createElement('div'); 22 | * let container = document.getElementById('container'); 23 | * 24 | * component.innerHTML = 'This is my component'; 25 | * container.innerHTML = ''; 26 | * 27 | * container.appendChild(component); 28 | * ``` 29 | * 30 | * API documentation: 31 | * 32 | * `React.createElement` creates a `ReactElement` and takes the following arguments: 33 | * - type: This can be an HTML tag name (e.g., 'div', 'span', etc), or a `ReactClass` 34 | * - props: This is an object defining the properties for the element 35 | * - children: This is the inner content of the element, which can be a string of text, or `ReactElement` 36 | * 37 | * `ReactDOM.render` will render an element to the DOM and takes two arguments: 38 | * - element: This is the `ReactElement` to be rendered. 39 | * - container: This is the DOM element to render to. 40 | * 41 | * Exercise: 42 | * 43 | * Create a `ReactElement` that uses a `div` as it's element and renders the 44 | * text "Hello World". 45 | */ 46 | 47 | ReactDOM.render( 48 | React.createElement('div', null, 'Hello World'), 49 | document.getElementById('container') 50 | ); 51 | 52 | /** 53 | * React offers a markup extension to make building declarative UIs more 54 | * familiar to those of us coming from an HTML background. This extension is 55 | * called JSX. This is completely optional when using React and requires a 56 | * transpiler like babel + babel-preset-react to run in the browser. This is 57 | * an acceptable trade off as you are likely already using babel + 58 | * babel-preset-es2015 to transpile your ES6 syntax to work in older browsers. 59 | * 60 | * Let's look at an example of JSX: 61 | * 62 | * ``` 63 | * ReactDOM.render(
This is my component
, document.getElementById('container')); 64 | * ``` 65 | * 66 | * I know what you're thinking: "Why is there HTML in my JavaScript?!". This is 67 | * a typical initial reaction. Remember it's optional to use JSX, though once 68 | * you get past the initial gag reflex, it will grow on you and you'll enjoy 69 | * the flavor :). 70 | * 71 | * It's important to note that this produces exactly what you see in the first 72 | * example once it has been transpiled. 73 | * 74 | * Exercise: 75 | * 76 | * Refactor your previous solution to use JSX. 77 | */ 78 | 79 | ReactDOM.render(
Hello World!
, document.getElementById('container')); 80 | -------------------------------------------------------------------------------- /exercises/exercise02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 2 - Props 6 | 7 | 8 |

Exercise 2 - Props

9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercises/exercise02/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | const container = document.getElementById('container'); 5 | 6 | /** 7 | * React components accept properties which allow us to pass data into a 8 | * component from the outside. This is very similar to using attributes 9 | * with native HTML elements. 10 | * 11 | * Example: 12 | * 13 | * ``` 14 | * ReactDOM.render( 15 | * 16 | * document.getElementById('container') 17 | * ); 18 | * ``` 19 | * 20 | * By passing in properties, the component can be used to hide away render 21 | * logic that may otherwise be repeated over and over, but still allows for 22 | * passing in information unique to a given instance. This is exactly the same 23 | * as declaring a function to calculate how old a person is, for example,and 24 | * passing in their birth date as an argument. 25 | * 26 | * In order to use props we have to define our component a little differently 27 | * than we have done in the previous example. 28 | * 29 | * ``` 30 | * let Hello = (props) => { 31 | * return
Hello {props.children}
; 32 | * }; 33 | * 34 | * ReactDOM.render(World, container); 35 | * ``` 36 | * 37 | * This example uses a reserved prop name `children` which will render whatever 38 | * is passed in as the body of the component. This is similar to using 39 | * "transclude" with an Angular directive. 40 | * 41 | * You also likely noticed the curly braces around `props.children`. This tells 42 | * JSX to evaluate what's between the braces as literal JavaScript. 43 | * 44 | * You may also accept any props you need for your component to render. 45 | * 46 | * ``` 47 | * let OrderDetail = (props) => { 48 | * return ( 49 | *
50 | *
Order Number: {props.order.number}
51 | *
Quantity: {props.order.quantity
52 | *
Order Total: {props.order.quantity * props.product.price}
53 | *
Product: {props.product.name}
54 | *
55 | *
56 | * ); 57 | * }; 58 | * 59 | * ReactDOM.render( 60 | * , container 71 | * ); 72 | * ``` 73 | * 74 | * Exercise: 75 | * 76 | * Create a component that takes a user's first and last name and renders 77 | * "Hello Jane Doe!" (assuming first name is Jane and last name is Doe). 78 | */ 79 | 80 | let SayHello = (props) => { 81 | return ( 82 |
Hello {props.firstName} {props.lastName}!
83 | ); 84 | }; 85 | 86 | ReactDOM.render(, container); 87 | 88 | /** 89 | * Often you will want to validate that properties that are provided to your 90 | * component. This could be anything from verifying the data type of a prop, 91 | * or could be indicating that a prop is required. React provides a mechanism 92 | * for specifying the validation of props as well as providing default values 93 | * in the event that a prop isn't required and no value is provided. This is 94 | * done using `propTypes` and `defaultProps`. 95 | * 96 | * Example: 97 | * 98 | * ``` 99 | * let OrderDetail = (props) => { 100 | * return ( 101 | *
102 | *
Order Number: {props.order.number}
103 | *
Quantity: {props.order.quantity
104 | *
Order Total: {props.order.quantity * props.product.price}
105 | *
Product: {props.product.name}
106 | *
107 | *
108 | * ); 109 | * }; 110 | * 111 | * OrderDetail.defaultProps = { 112 | * order: {}, 113 | * product: {} 114 | * }; 115 | * 116 | * OrderDetail.propTypes = { 117 | * order: PropTypes.object, 118 | * product: PropTypes.object 119 | * }; 120 | * 121 | * ReactDom.render(, container); 122 | * ``` 123 | * 124 | * See https://facebook.github.io/react/docs/reusable-components.html#prop-validation 125 | * 126 | * Exercise: 127 | * 128 | * Create a component that takes a user object as a prop, provides a default 129 | * value, validates that the prop is an object, and renders 130 | * "Hello firstName lastName!". 131 | */ 132 | 133 | let ValidateHello = (props) => { 134 | return ( 135 |
Hello {props.user.firstName} {props.user.lastName}
136 | ); 137 | }; 138 | 139 | ValidateHello.defaultProps = { 140 | user: {} 141 | }; 142 | 143 | ValidateHello.propTypes = { 144 | user: PropTypes.shape({ 145 | firstName: PropTypes.string, 146 | lastName: PropTypes.string 147 | }) 148 | }; 149 | 150 | ReactDOM.render(, container); 151 | -------------------------------------------------------------------------------- /exercises/exercise03/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 3 - Styles 6 | 26 | 27 | 28 |

Exercise 3 - Styles

29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /exercises/exercise03/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | const container = document.getElementById('container'); 5 | 6 | /** 7 | * React components are styled very similarly to how you would natively style 8 | * an HTML element. You have two options: CSS classes and inline styles. These 9 | * styles are passed into a component by using props as we just learned about. 10 | * 11 | * Example: 12 | * 13 | * ``` 14 | * ReactDOM.render(, container); 15 | * ``` 16 | * 17 | * You may have noticed that the prop name is `className` as opposed to `class`. 18 | * This is to avoid conflicts with the reserved word `class` in JavaScript. 19 | * Using the `className` property will apply the given CSS class to the element. 20 | * 21 | * As mentioned you can also use inline styles. This approach allows you to set 22 | * styles directly to an element without using CSS classes, but rather by 23 | * defining them as an object. 24 | * 25 | * Example: 26 | * 27 | * ``` 28 | * ReactDOM.render( 29 | * , container 34 | * ); 35 | * ``` 36 | * 37 | * When using inline styles CSS property names that contain a hyphen shohuld be 38 | * converted to camel case (e.g., font-size -> fontSize). Also by default any 39 | * property that has a numeric value will be assumed to have a unit of `px`. 40 | * 41 | * Exercise: 42 | * 43 | * Create a `Box` component that renders a `div` and accepts a `size` and 44 | * `style` property. Some CSS classes are provided: 45 | * 46 | * - Box: provides basic styling for the component 47 | * - Box--large: renders a large box 48 | * - Box--medium: renders a medium box 49 | * - Box--small: renders a small box 50 | * 51 | * If the `size` is `large` the component should use the class `Box--large`. 52 | * It should also use whatever inline styles are provided. 53 | */ 54 | 55 | let Box = (props) => { 56 | return ( 57 |
58 | {props.children} 59 |
60 | ); 61 | }; 62 | 63 | Box.propTypes = { 64 | size: PropTypes.string, 65 | style: PropTypes.object 66 | }; 67 | 68 | ReactDOM.render(, container); 69 | -------------------------------------------------------------------------------- /exercises/exercise04/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 4 - Composing Components 6 | 51 | 52 | 53 | 54 |

Exercise 4 - Composing Components

55 |
56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /exercises/exercise04/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | const container = document.getElementById('container'); 5 | 6 | /** 7 | * As mentioned at the begining, everything in React starts with a component. 8 | * Using components as the building block, we can assemble multiple 9 | * components together to create more complex components, or apps. 10 | * 11 | * Up until now we have only created components that render native DOM elements 12 | * as their content. Once we have our own custom component however, it is 13 | * exactly the same to render that instead of a native element. 14 | * 15 | * Example: 16 | * 17 | * ``` 18 | * let FormField = (props) => { 19 | * return ( 20 | *
21 | * 22 | * {props.children} 23 | *
24 | * ); 25 | * }; 26 | * 27 | * let LoginForm = () => { 28 | * return ( 29 | *
30 | * 31 | * 32 | * 33 | * 34 | * 35 | * 36 | * 37 | *
38 | * ); 39 | * }; 40 | * 41 | * ReactDOM.render(, container); 42 | * ``` 43 | * 44 | * We could now potentially move `FormField` into it's own file which would 45 | * allow it to be tested in isolation as well as be imported anywhere else 46 | * in our application that it may be needed. 47 | * 48 | * Exercise: 49 | * 50 | * Create a `Person` component using the following spec. 51 | * 52 | * `Person`: 53 | * - Render a `div` with a `className` of `Person` 54 | * - Accept the props `name`, `title`, `avatar`, `twitter`, `github` 55 | * - Render the name and title of the person 56 | * - Render the `Avatar` 57 | * - Render links to twitter and github using `Icon` 58 | * 59 | * `Avatar`: 60 | * - Render a `img` with a `className` of `Avatar` 61 | * - Accept the props `size` and `url` 62 | * - Adjust the width/height of img according to size 63 | * 64 | * `Icon`: 65 | * - Render an `a` with a `className` of `Icon` 66 | * - Accept the props `href` and `type` 67 | * - Render a font-awesome icon based on `type` 68 | */ 69 | 70 | let Person = (props) => { 71 | return ( 72 |
73 | 74 | {props.name} 75 | {props.title} 76 |
    77 |
  • 78 |
  • 79 |
80 |
81 | ); 82 | } 83 | 84 | Person.propTypes = { 85 | avatar: PropTypes.string, 86 | name: PropTypes.string, 87 | title: PropTypes.string, 88 | twitter: PropTypes.string, 89 | github: PropTypes.string 90 | }; 91 | 92 | let Avatar = (props) => { 93 | return ( 94 | 99 | ); 100 | } 101 | 102 | Avatar.defaultProps = { 103 | size: 200 104 | }; 105 | 106 | Avatar.propTypes = { 107 | url: PropTypes.string, 108 | size: PropTypes.number 109 | }; 110 | 111 | let Icon = (props) => { 112 | return ( 113 | 114 | 115 | 116 | ); 117 | }; 118 | 119 | ReactDOM.render( 120 | , container 127 | ); 128 | -------------------------------------------------------------------------------- /exercises/exercise05/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 5 - State 6 | 25 | 26 | 27 |

Exercise 5 - State

28 |
29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /exercises/exercise05/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | const container = document.getElementById('container'); 5 | 6 | /** 7 | * Where props allow for data to be passed into a component from the outside, 8 | * state allows a component to keep track of it's own data needed for rendering. 9 | * 10 | * In order for a component to use state we need to use a more complex way of 11 | * creating a component. 12 | * 13 | * Example: 14 | * 15 | * ``` 16 | * class ClickCounter extends Component { 17 | * constructor(props) { 18 | * super(props); 19 | * 20 | * this.state = { 21 | * clicks: 0 22 | * }; 23 | * 24 | * this.handleButtonClick = this.handleButtonClick.bind(this); 25 | * } 26 | * 27 | * handleButtonClick() { 28 | * this.setState({ 29 | * clicks: this.state.clicks + 1 30 | * }); 31 | * } 32 | * 33 | * render() { 34 | * return ( 35 | *
36 | * 37 | *
Clicked {this.state.clicks} times
38 | *
39 | * ); 40 | * } 41 | * } 42 | * ``` 43 | * 44 | * Calling `setState` will queue the internal state for the component to be 45 | * updated. Once it has changed `render` will be called and the component will 46 | * once again be rendered with the changes. Only this point of the render tree 47 | * down will be affected. 48 | * 49 | * There are a handful of lifecycle methods that come into play when dealing 50 | * with state, such as `componentWillUpdate`, `componentDidUpdate`, etc. 51 | * 52 | * See https://facebook.github.io/react/docs/component-specs.html 53 | * 54 | * Exercise: 55 | * 56 | * Create a `StopWatch` component that has a Start/Stop button and a Clear 57 | * button. Pressing Start will start a timer and the lapsed time in 58 | * milliseconds should be displayed above the buttons. Once started the 59 | * Start button should change to Stop. Clicking Stop will stop the timer 60 | * but lapsed time will be preserved. Clicking Start again will resume 61 | * the timer from where it left off. Clicking Clear will stop the timer 62 | * if it's running and reset the lapsed time to 0. 63 | */ 64 | 65 | class StopWatch extends Component { 66 | constructor() { 67 | super(...arguments); 68 | 69 | this.state = { 70 | running: false, 71 | lapse: 0, 72 | now: 0 73 | }; 74 | 75 | this.timer = null; 76 | this.handleRunClick = this.handleRunClick.bind(this); 77 | this.handleClearClick = this.handleClearClick.bind(this); 78 | } 79 | 80 | handleRunClick() { 81 | if (this.state.running) { 82 | this.stop(); 83 | } else { 84 | this.start(); 85 | } 86 | } 87 | 88 | handleClearClick() { 89 | this.stop(); 90 | this.setState({ 91 | lapse: 0, 92 | now: 0 93 | }); 94 | } 95 | 96 | start() { 97 | this.timer = setInterval(() => { 98 | this.setState({ 99 | lapse: Date.now() - this.state.now 100 | }, 1); 101 | }); 102 | 103 | this.setState({ 104 | running: true, 105 | now: Date.now() - this.state.lapse 106 | }); 107 | } 108 | 109 | stop() { 110 | clearInterval(this.timer); 111 | this.timer = null; 112 | 113 | this.setState({ 114 | running: false 115 | }); 116 | } 117 | 118 | render() { 119 | return ( 120 |
121 | 122 | 123 | 124 |
125 | ); 126 | } 127 | } 128 | 129 | ReactDOM.render(, container); 130 | 131 | -------------------------------------------------------------------------------- /exercises/exercise06/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 6 - Fetching Data 6 | 7 | 8 |

Exercise 6 - Fetching Data

9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercises/exercise06/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import axios from 'axios'; 4 | 5 | const container = document.getElementById('container'); 6 | 7 | /** 8 | * React does not provide a module for sending HTTP requests. To do this we 9 | * need to bring in a third-party library. For this exercies we will use a 10 | * libary called `axios`. 11 | * 12 | * It is best not to fetch data from a server in the `render` method. As we 13 | * saw in the last exercise any change to the state of a component can cause 14 | * a re-render of the component. This will likely happen more often than we 15 | * want. It is best to use another lifecycle method `componentWillMount` to 16 | * make these requests. This method will be called once before the component 17 | * is inserted into the document, regardless of how many times `render` is 18 | * called. 19 | * 20 | * Example: 21 | * 22 | * ``` 23 | * class UserProfile extends Component { 24 | * constructor(props) { 25 | * super(props); 26 | * 27 | * this.state = { 28 | * user: {} 29 | * }; 30 | * } 31 | * 32 | * componentWillMount() { 33 | * axios(`/users/${this.props.id}`) 34 | * .then((response) => { 35 | * this.setState({ 36 | * user: response.data 37 | * }); 38 | * }); 39 | * } 40 | * 41 | * render() { 42 | * let user = this.state.user; 43 | * return ( 44 | *
45 | *
First name: ${user.firstName}
46 | *
Last name: ${user.lastName}
47 | *
Email address: ${user.emailAddress
48 | *
49 | * ); 50 | * } 51 | * } 52 | * 53 | * UserProfile.propTypes = { 54 | * id: PropTypes.number.isRequired 55 | * }; 56 | * ``` 57 | * 58 | * See https://facebook.github.io/react/docs/component-specs.html 59 | * 60 | * Exercise: 61 | * 62 | * Create a `RepoList` component that lists all the GitHub repos for a user. 63 | * Allow the user to be provided as a prop. 64 | * 65 | * https://api.github.com/users/{username}/repos 66 | */ 67 | 68 | class RepoList extends Component { 69 | constructor(props) { 70 | super(props); 71 | 72 | this.state = { 73 | repos: [] 74 | }; 75 | } 76 | 77 | componentWillMount() { 78 | axios(`https://api.github.com/users/${this.props.user}/repos?per_page=250`) 79 | .then((response) => { 80 | this.setState({ 81 | repos: response.data 82 | }); 83 | }); 84 | } 85 | 86 | render() { 87 | return ( 88 |
    89 | {this.state.repos.map((repo) => { 90 | return
  • {repo.name}
  • ; 91 | })} 92 |
93 | ); 94 | } 95 | } 96 | 97 | RepoList.propTypes = { 98 | user: PropTypes.string 99 | }; 100 | 101 | ReactDOM.render(, container); 102 | -------------------------------------------------------------------------------- /exercises/exercise07/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exercise 7 - React Router 6 | 7 | 8 |

Exercise 7 - React Router

9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercises/exercise07/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Router, Route, IndexRoute, Link, hashHistory, useRouterHistory } from 'react-router'; 4 | import { createHashHistory } from 'history'; 5 | 6 | const container = document.getElementById('container'); 7 | const USERS = [ 8 | { 9 | id: 1, 10 | firstName: 'Fred', 11 | lastName: 'Flintstone', 12 | avatar: 'https://upload.wikimedia.org/wikipedia/en/thumb/a/ad/Fred_Flintstone.png/165px-Fred_Flintstone.png' 13 | }, 14 | { 15 | id: 2, 16 | firstName: 'Wilma', 17 | lastName: 'Flintstone', 18 | avatar: 'https://upload.wikimedia.org/wikipedia/en/9/97/Wilma_Flintstone.png' 19 | }, 20 | { 21 | id: 3, 22 | firstName: 'Pebbles', 23 | lastName: 'Flintstone', 24 | avatar: 'https://upload.wikimedia.org/wikipedia/en/thumb/0/0a/Pebbles_Flintstone.png/155px-Pebbles_Flintstone.png' 25 | }, 26 | { 27 | id: 4, 28 | firstName: 'Barney', 29 | lastName: 'Rubble', 30 | avatar: 'https://upload.wikimedia.org/wikipedia/en/thumb/e/e2/Barney_Rubble.png/160px-Barney_Rubble.png' 31 | }, 32 | { 33 | id: 5, 34 | firstName: 'Betty', 35 | lastName: 'Rubble', 36 | avatar: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Betty_Rubble.png' 37 | }, 38 | { 39 | id: 6, 40 | firstName: 'Bamm-Bamm', 41 | lastName: 'Rubble', 42 | avatar: 'https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/Bamm-Bamm_Rubble.png/180px-Bamm-Bamm_Rubble.png' 43 | } 44 | ]; 45 | 46 | /** 47 | * Client side routing for single page apps with React is made possible with 48 | * third-party libraries like `react-router`. Using a client router like this 49 | * allows you to link to different parts of your app without reloading the page. 50 | * React router behaves similarly to server side libraries like `express` where 51 | * you define the path to a route and then specify how that route should be 52 | * handled. 53 | * 54 | * Example: 55 | * 56 | * ``` 57 | * let Home = () => Home; 58 | * let ProductList = () => Product List; 59 | * let ProductDetail = (props) => Product Detail (props.routeParams.productId); 60 | * 61 | * let Routes = ( 62 | * 63 | * 64 | * 65 | * 66 | * 67 | * ); 68 | * 69 | * ReactDOM.render(Routes, component); 70 | * ``` 71 | * 72 | * In this example we have three routes defined, a home route, a product list, 73 | * and a product detail. If the `path` prop matches the current location 74 | * then the `component` for that route will be rendered. 75 | * 76 | * You may also notice the product detail `path` contains `:productId`. 77 | * This is a named parameter portion of the path. If the location were 78 | * `/product/37` then the product detail route would be matched and rendered. 79 | * 80 | * You will also notice that the `ProductDetail` component is also given a 81 | * prop called `routeParams`. This is provided by `react-router` and contains 82 | * all the named parameters that are defined in the route's path. 83 | * 84 | * You can create links within your app to these routes using the `Link` 85 | * component provided by `react-router`. 86 | * 87 | * Example: 88 | * 89 | * ``` 90 | * let ProductList = () => { 91 | * return ( 92 | *
    93 | *
  • Product 1
  • 94 | *
  • Product 2
  • 95 | *
  • Product 3
  • 96 | *
  • Product 4
  • 97 | *
98 | * ); 99 | * }; 100 | * ``` 101 | * 102 | * Exercise: 103 | * 104 | * Create an app that uses `react-router`. The app should have a `/` route, 105 | * a `/users` route which lists all the `USERS`, and a `/user/:userId` route 106 | * that shows the details for the user matching `userId` parameter. 107 | */ 108 | 109 | let App = (props) => { 110 | return ( 111 |
112 |
    113 |
  • Home
  • 114 |
  • Users
  • 115 |
116 |
{props.children}
117 |
118 | ); 119 | } 120 | 121 | let Home = () => { 122 | return

Welcome!

123 | } 124 | 125 | let NotFound = () => { 126 | return

Not Found

127 | } 128 | 129 | let Users = () => { 130 | return ( 131 |
132 |

Users

133 |
    134 | {USERS.map((user) => { 135 | return ( 136 |
  • 137 | {user.firstName} {user.lastName} 138 |
  • 139 | ); 140 | })} 141 |
142 |
143 | ); 144 | } 145 | 146 | let UserProfile = (props) => { 147 | let id = props.routeParams.userId 148 | let user = USERS.filter((user) => { 149 | return user.id == id; 150 | })[0]; 151 | 152 | return ( 153 |
154 |

User

155 | « Users 156 | {user ? ( 157 |
158 |
159 | {user.firstName} {user.lastName} 160 |
161 | ) : ( 162 |
User Not Found ({id})
163 | )} 164 |
165 | ); 166 | } 167 | 168 | let appHistory = useRouterHistory(createHashHistory)({ queryKey: false }); 169 | 170 | ReactDOM.render(( 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | ), container); 180 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-workshop", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --inline --content-base exercises/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/mzabriskie/react-workshop.git" 13 | }, 14 | "author": "Matt Zabriskie", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/mzabriskie/react-workshop/issues" 18 | }, 19 | "homepage": "https://github.com/mzabriskie/react-workshop#readme", 20 | "dependencies": { 21 | "axios": "^0.13.1", 22 | "history": "^2.1.2", 23 | "react": "^15.3.0", 24 | "react-dom": "^15.3.0", 25 | "react-router": "^2.6.1" 26 | }, 27 | "devDependencies": { 28 | "babel-loader": "^6.2.4", 29 | "babel-preset-es2015": "^6.13.2", 30 | "babel-preset-react": "^6.11.1", 31 | "webpack": "^1.13.1", 32 | "webpack-dev-server": "^1.14.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | var EXERCISES_DIR = path.resolve(process.cwd(), 'exercises'); 5 | 6 | function buildEntries() { 7 | return fs.readdirSync(EXERCISES_DIR).reduce(function (entries, dir) { 8 | var file = path.join(EXERCISES_DIR, dir, 'index.js'); 9 | 10 | if (fs.existsSync(file)) { 11 | entries[dir] = file; 12 | } 13 | 14 | return entries; 15 | }, {}); 16 | } 17 | 18 | module.exports = { 19 | 20 | entry: buildEntries(), 21 | 22 | output: { 23 | filename: '[name].js', 24 | chunkFilename: '[id].chunk.js', 25 | path: 'exercises/__build__', 26 | publicPath: '/__build__/' 27 | }, 28 | 29 | module: { 30 | loaders: [ 31 | { 32 | test: /\.js$/, 33 | exclude: /node_modules/, 34 | loader: 'babel', 35 | query: { 36 | presets: ['react', 'es2015'] 37 | } 38 | } 39 | ] 40 | }, 41 | 42 | plugins: [ 43 | new webpack.optimize.CommonsChunkPlugin('shared.js') 44 | ] 45 | 46 | }; 47 | --------------------------------------------------------------------------------