├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package.json ├── server.js ├── src ├── App.js ├── DeckController.js ├── components │ ├── Center.js │ ├── Center.less │ ├── Deck.js │ ├── Deck.less │ ├── Image.js │ ├── Image.less │ ├── Slide.js │ └── Slide.less ├── favicon.ico ├── index.js ├── index.less ├── normalize.less ├── slides │ ├── actionCreators.js │ ├── actionTypes.js │ ├── bundle1.png │ ├── bundle2.png │ ├── community.png │ ├── community0.png │ ├── escher.jpg │ ├── hor.png │ ├── hor2.png │ ├── hor3.png │ ├── hor4.png │ ├── hor5.png │ ├── index.js │ ├── logo.png │ ├── middleware.png │ ├── middleware2.png │ ├── middleware3.png │ ├── middleware4.png │ ├── middleware5.png │ ├── middleware6.png │ ├── middleware7.png │ ├── middleware8.png │ ├── middleware9.png │ ├── next.png │ ├── npm.png │ ├── reducer.png │ ├── reducers.js │ ├── selector.png │ ├── setstate2.png │ ├── store.png │ ├── store2.png │ ├── store3.png │ ├── store4.png │ ├── store5.png │ ├── store6.png │ ├── store7.png │ ├── store8.png │ ├── tom.jpg │ └── twitter.png └── theme.less ├── webpack.config.js └── webpack.prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "es6": true, 9 | "node": true 10 | }, 11 | "parser": "babel-eslint", 12 | "rules": { 13 | "eol-last": 2, 14 | "max-len": [2, 80, 4], 15 | "no-underscore-dangle": [0], 16 | "no-var": 2, 17 | "new-cap": 0, 18 | "react/display-name": 0, 19 | "react/jsx-boolean-value": 2, 20 | "react/jsx-quotes": 2, 21 | "react/jsx-no-undef": 2, 22 | "react/jsx-sort-props": 0, 23 | "react/jsx-uses-react": 2, 24 | "react/jsx-uses-vars": 2, 25 | "react/no-did-mount-set-state": 2, 26 | "react/no-did-update-set-state": 2, 27 | "react/no-multi-comp": 0, 28 | "react/no-unknown-property": 2, 29 | "react/react-in-jsx-scope": 2, 30 | "react/self-closing-comp": 2, 31 | "react/wrap-multilines": 2, 32 | "quotes": [2, "single"], 33 | "space-before-function-paren": [2, { 34 | "anonymous": "always", 35 | "named": "never" 36 | }], 37 | "strict": [2, "global"] 38 | }, 39 | "plugins": [ 40 | "react" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dan Abramov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Redux Journey 2 | 3 | ## [Watch the Talk](https://youtu.be/uvAXVMwHJXU) 4 | ## [View the Slides](http://gaearon.github.io/the-redux-journey) 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Redux Journey 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-redux-jorney", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "node server.js", 6 | "build": "webpack --config webpack.prod.config.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/gaearon/the-redux-journey.git" 11 | }, 12 | "author": "Dan Abramov (http://github.com/gaearon)", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "babel-core": "^5.1.11", 16 | "babel-eslint": "^3.1.7", 17 | "babel-loader": "^5.0.0", 18 | "css-loader": "^0.13.1", 19 | "eslint": "^0.21.2", 20 | "eslint-plugin-react": "^2.3.0", 21 | "file-loader": "^0.8.4", 22 | "image-webpack-loader": "^1.8.0", 23 | "less": "^2.5.1", 24 | "less-loader": "^2.2.0", 25 | "react-hot-loader": "^1.2.2", 26 | "style-loader": "^0.12.2", 27 | "webpack": "^1.7.2", 28 | "webpack-dev-server": "^1.7.0" 29 | }, 30 | "dependencies": { 31 | "classnames": "^1.2.2", 32 | "lodash": "^3.9.3", 33 | "object-assign": "^2.0.0", 34 | "react": "^15.1.0", 35 | "react-dom": "^15.1.0", 36 | "react-modal": "^0.2.0", 37 | "react-redux": "^4.4.5", 38 | "redux": "^3.5.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true, 9 | watchDelay: 0, 10 | stats: { colors: true } 11 | }).listen(3000, 'localhost', function (err, result) { 12 | if (err) { 13 | console.log(err); 14 | } 15 | 16 | console.log('Listening at localhost:3000'); 17 | }); 18 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DeckController from './DeckController'; 3 | import { connect } from 'react-redux'; 4 | import * as actionCreators from './slides/actionCreators'; 5 | import './normalize.less'; 6 | import './index.less'; 7 | 8 | export default connect( 9 | state => state, 10 | actionCreators 11 | )(DeckController); 12 | -------------------------------------------------------------------------------- /src/DeckController.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Deck from './components/Deck'; 3 | import Slide from './components/Slide'; 4 | import getSlides from './slides'; 5 | 6 | export default class DeckController extends Component { 7 | constructor() { 8 | super(); 9 | this.handleKeyDown = this.handleKeyDown.bind(this); 10 | } 11 | 12 | componentDidMount() { 13 | window.addEventListener('keydown', this.handleKeyDown); 14 | } 15 | 16 | handleKeyDown(e) { 17 | switch (e.keyCode) { 18 | case 37: 19 | this.props.prevSlide(); 20 | break; 21 | case 39: 22 | this.props.nextSlide(); 23 | break; 24 | } 25 | } 26 | 27 | render() { 28 | return ( 29 | 30 | {this.renderSlides()} 31 | 32 | ); 33 | } 34 | 35 | renderSlides() { 36 | return getSlides().map((content, index) => 37 | {content} 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Center.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import './Center.less'; 3 | 4 | export default class Center extends Component { 5 | static propTypes = { 6 | wide: PropTypes.bool, 7 | gradient: PropTypes.bool 8 | }; 9 | 10 | render() { 11 | const { wide, alt, code, gradient, title } = this.props; 12 | return ( 13 |
22 |
23 | {this.props.children} 24 |
25 |
26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Center.less: -------------------------------------------------------------------------------- 1 | @import ".././theme.less"; 2 | 3 | .Center { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | 10 | background-color: @base07; 11 | color: @base01; 12 | 13 | h1,h2,h3,h4,h5,h6 { 14 | color: @base00; 15 | } 16 | } 17 | 18 | .Center.Center--alt { 19 | background-color: @base00; 20 | color: @base06; 21 | 22 | h1,h2,h3,h4,h5,h6 { 23 | color: @base07; 24 | } 25 | } 26 | 27 | .Center.Center--gradient { 28 | background: linear-gradient(to top, @base00, @base07); 29 | } 30 | 31 | 32 | .Center.Center--code { 33 | background-color: #1B2B34; 34 | } 35 | 36 | .Center-content { 37 | max-width: 55%; 38 | text-align: center; 39 | } 40 | 41 | .Center--wide .Center-content { 42 | max-width: 80%; 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Deck.js: -------------------------------------------------------------------------------- 1 | import React, { cloneElement, Component, Children, PropTypes } from 'react'; 2 | import './Deck.less'; 3 | 4 | const iOS = /iphone|ipod|ipad/.test(window.navigator.userAgent.toLowerCase()); 5 | 6 | export default class Deck extends Component { 7 | static propTypes = { 8 | currentSlide: PropTypes.number.isRequired 9 | }; 10 | 11 | render() { 12 | const { children, currentSlide } = this.props; 13 | const elements = []; 14 | 15 | Children.forEach(children, (child, index) => { 16 | if (index === currentSlide) { 17 | elements.unshift( 18 | cloneElement(child, { key: 'current' }) 19 | ); 20 | } else if (!iOS) { 21 | elements.push( 22 | this.preloadImages(child, index) 23 | ); 24 | } 25 | }); 26 | 27 | 28 | return ( 29 |
30 |
41 | {elements} 42 |
53 |
54 | ); 55 | } 56 | 57 | preloadImages(slide, index) { 58 | return ( 59 |
68 | {slide} 69 |
70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Deck.less: -------------------------------------------------------------------------------- 1 | .Deck { 2 | height: 100vh; 3 | width: 100vw; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Image.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './Image.less'; 3 | 4 | export default class Image extends Component { 5 | render() { 6 | return ( 7 | 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Image.less: -------------------------------------------------------------------------------- 1 | .Image { 2 | max-width: 100%; 3 | } -------------------------------------------------------------------------------- /src/components/Slide.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './Slide.less'; 3 | 4 | export default class Slide extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 | {this.props.children} 10 |
11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Slide.less: -------------------------------------------------------------------------------- 1 | @import ".././theme.less"; 2 | 3 | .Slide { 4 | width: 100vw; 5 | height: 100vh; 6 | } 7 | 8 | .Slide-content { 9 | position: relative; 10 | width: 100%; 11 | height: 100%; 12 | overflow: hidden; 13 | } 14 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/favicon.ico -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './favicon.ico'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './App'; 5 | import { createStore } from 'redux'; 6 | import { Provider } from 'react-redux'; 7 | import reducer from './slides/reducers'; 8 | 9 | const store = createStore( 10 | reducer, 11 | (window.devToolsExtension ? 12 | window.devToolsExtension() : 13 | (f => f) 14 | ) 15 | ); 16 | 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | document.getElementById('root') 22 | ); 23 | 24 | if (module.hot) { 25 | module.hot.accept('./slides/reducers', () => { 26 | const nextReducer = require('./slides/reducers'); 27 | store.replaceReducer(nextReducer); 28 | }); 29 | } -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | @import "theme.less"; 2 | 3 | body { 4 | text-rendering: optimizeLegibility; 5 | font-family: @font-main; 6 | font-size: @fontsize-root; 7 | } 8 | 9 | @media only screen 10 | and (min-device-width : 375px) 11 | and (max-device-width : 667px) { 12 | body { 13 | font-size: 1.75em; 14 | } 15 | } 16 | 17 | code { 18 | font-family: @font-code; 19 | } 20 | 21 | blockquote { 22 | font-weight: bold; 23 | } 24 | 25 | ul { 26 | text-align: left; 27 | } 28 | 29 | * { 30 | box-sizing: border-box; 31 | } 32 | -------------------------------------------------------------------------------- /src/normalize.less: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ 2 | 3 | // * 1. Set default font family to sans-serif. 4 | // * 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. 5 | html { 6 | font-family: sans-serif; 7 | -ms-text-size-adjust: 100%; 8 | -webkit-text-size-adjust: 100%; 9 | } 10 | 11 | // Remove default margin. 12 | body { margin: 0 } 13 | 14 | // HTML5 display definitions ========================================================================== 15 | // * Correct `block` display not defined for any HTML5 element in IE 8/9. 16 | // * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox. 17 | // * Correct `block` display not defined for `main` in IE 11. 18 | article, 19 | aside, 20 | details, 21 | figcaption, 22 | figure, 23 | footer, 24 | header, 25 | hgroup, 26 | main, 27 | nav, 28 | section, 29 | summary { 30 | display: block; 31 | } 32 | 33 | // * 1. Correct `inline-block` display not defined in IE 8/9. 34 | // * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 35 | audio, 36 | canvas, 37 | progress, 38 | video { 39 | display: inline-block; 40 | vertical-align: baseline; 41 | } 42 | 43 | // * Prevent modern browsers from displaying `audio` without controls. 44 | // * Remove excess height in iOS 5 devices. 45 | audio:not([controls]) { 46 | display: none; 47 | height: 0; 48 | } 49 | 50 | // * Address `[hidden]` styling not present in IE 8/9/10. 51 | // * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 52 | [hidden], 53 | template { 54 | display: none; 55 | } 56 | 57 | // Links ========================================================================== 58 | // * Remove the gray background color from active links in IE 10. 59 | a { 60 | background: transparent; 61 | // * Improve readability when focused and also mouse hovered in all browsers. 62 | &:active, 63 | &:hover { 64 | outline: 0; 65 | } 66 | } 67 | 68 | // Text-level semantics ========================================================================== 69 | 70 | // * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 71 | abbr[title] { border-bottom: 1px dotted; } 72 | 73 | // * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 74 | b, 75 | strong { 76 | font-weight: bold; 77 | } 78 | 79 | // * Address styling not present in Safari and Chrome. 80 | dfn { font-style: italic; } 81 | 82 | // * Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari, and Chrome. 83 | h1 { 84 | font-size: 2em; 85 | margin: 0.67em 0; 86 | } 87 | 88 | // * Address styling not present in IE 8/9. 89 | mark { 90 | background: #ff0; 91 | color: #000; 92 | } 93 | 94 | // * Address inconsistent and variable font size in all browsers. 95 | small { font-size: 80%; } 96 | 97 | // * Prevent `sub` and `sup` affecting `line-height` in all browsers. 98 | sub, 99 | sup { 100 | font-size: 75%; 101 | line-height: 0; 102 | position: relative; 103 | vertical-align: baseline; 104 | } 105 | 106 | sup { top: -0.5em; } 107 | sub { bottom: -0.25em; } 108 | 109 | // Embedded content ========================================================================== 110 | // * Remove border when inside `a` element in IE 8/9/10. 111 | img { border: 0; } 112 | 113 | // * Correct overflow not hidden in IE 9/10/11. 114 | svg:not(:root) { overflow: hidden; } 115 | 116 | // Grouping content ========================================================================== 117 | // * Address margin not present in IE 8/9 and Safari. 118 | figure { margin: 1em 40px; } 119 | 120 | // * Address differences between Firefox and other browsers. 121 | hr { 122 | -moz-box-sizing: content-box; 123 | box-sizing: content-box; 124 | height: 0; 125 | } 126 | 127 | // * Contain overflow in all browsers. 128 | pre { overflow: auto; } 129 | 130 | // * Address odd `em`-unit font size rendering in all browsers. 131 | code, 132 | kbd, 133 | pre, 134 | samp { 135 | font-family: monospace, monospace; 136 | font-size: 1em; 137 | } 138 | 139 | // Forms ========================================================================== 140 | // * Known limitation: by default, Chrome and Safari on OS X allow very limited 141 | // * styling of `select`, unless a `border` property is set. 142 | 143 | // * 1. Correct color not being inherited. 144 | // * Known issue: affects color of disabled elements. 145 | // * 2. Correct font properties not being inherited. 146 | // * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 147 | button, 148 | input, 149 | optgroup, 150 | select, 151 | textarea { 152 | color: inherit; 153 | font: inherit; 154 | margin: 0; 155 | } 156 | 157 | // * Address `overflow` set to `hidden` in IE 8/9/10/11. 158 | button { overflow: visible;} 159 | 160 | // * Address inconsistent `text-transform` inheritance for `button` and `select`. 161 | // * All other form control elements do not inherit `text-transform` values. 162 | // * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 163 | // * Correct `select` style inheritance in Firefox. 164 | button, 165 | select { 166 | text-transform: none; 167 | } 168 | 169 | // * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 170 | // * 2. Correct inability to style clickable `input` types in iOS. 171 | // * 3. Improve usability and consistency of cursor style between image-type `input` and others. 172 | button, 173 | html input[type="button"] { 174 | -webkit-appearance: button; 175 | cursor: pointer; 176 | } 177 | 178 | // * Re-set default cursor for disabled elements. 179 | button[disabled], 180 | html input[disabled] { 181 | cursor: default; 182 | } 183 | 184 | // * Remove inner padding and border in Firefox 4+. 185 | button 186 | input { 187 | &::-moz-focus-inner { 188 | border: 0; 189 | padding: 0; 190 | } 191 | } 192 | 193 | // * Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. 194 | input { 195 | line-height: normal; 196 | &[type="reset"], 197 | &[type="submit"] { 198 | -webkit-appearance: button; 199 | cursor: pointer; 200 | } 201 | 202 | // * It's recommended that you don't attempt to style these elements. 203 | // * Firefox's implementation doesn't respect box-sizing, padding, or width. 204 | // * 1. Address box sizing set to `content-box` in IE 8/9/10. 205 | // * 2. Remove excess padding in IE 8/9/10. 206 | &[type="checkbox"], 207 | &[type="radio"] { 208 | box-sizing: border-box; 209 | padding: 0; 210 | } 211 | 212 | // * Fix the cursor style for Chrome's increment/decrement buttons. For certain 213 | // * `font-size` values of the `input`, it causes the cursor style of the 214 | // * decrement button to change from `default` to `text`. 215 | &[type="number"] { 216 | &::-webkit-inner-spin-button, 217 | &::-webkit-outer-spin-button { 218 | height: auto; 219 | } 220 | } 221 | 222 | // * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 223 | // * 2. Address `box-sizing` set to `border-box` in Safari and Chrome (include `-moz` to future-proof). 224 | &[type="search"] { 225 | -webkit-appearance: textfield; 226 | -moz-box-sizing: content-box; 227 | -webkit-box-sizing: content-box; 228 | box-sizing: content-box; 229 | 230 | // * Remove inner padding and search cancel button in Safari and Chrome on OS X. 231 | // * Safari (but not Chrome) clips the cancel button when the search input has 232 | // * padding (and `textfield` appearance). 233 | &::-webkit-search-cancel-button, 234 | &::-webkit-search-decoration { 235 | -webkit-appearance: none; 236 | } 237 | } 238 | 239 | } 240 | 241 | // * Define consistent border, margin, and padding. 242 | fieldset { 243 | border: 1px solid #c0c0c0; 244 | margin: 0 2px; 245 | padding: 0.35em 0.625em 0.75em; 246 | } 247 | 248 | // * 1. Correct `color` not being inherited in IE 8/9/10/11. 249 | // * 2. Remove padding so people aren't caught out if they zero out fieldsets. 250 | legend { 251 | border: 0; 252 | padding: 0; 253 | } 254 | 255 | // * Remove default vertical scrollbar in IE 8/9/10/11. 256 | textarea { overflow: auto; } 257 | 258 | // * Don't inherit the `font-weight` (applied by a rule above). 259 | // * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 260 | optgroup { font-weight: bold; } 261 | 262 | // Tables ========================================================================== 263 | // * Remove most spacing between table cells. 264 | table { 265 | border-collapse: collapse; 266 | border-spacing: 0; 267 | } 268 | 269 | td, 270 | th { 271 | padding: 0; 272 | } -------------------------------------------------------------------------------- /src/slides/actionCreators.js: -------------------------------------------------------------------------------- 1 | import * as types from './actionTypes'; 2 | 3 | export function prevSlide() { 4 | return { type: types.PREV_SLIDE }; 5 | } 6 | 7 | export function nextSlide() { 8 | return { type: types.NEXT_SLIDE }; 9 | } 10 | -------------------------------------------------------------------------------- /src/slides/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const NEXT_SLIDE = 'NEXT_SLIDE'; 2 | export const PREV_SLIDE = 'PREV_SLIDE'; 3 | 4 | -------------------------------------------------------------------------------- /src/slides/bundle1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/bundle1.png -------------------------------------------------------------------------------- /src/slides/bundle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/bundle2.png -------------------------------------------------------------------------------- /src/slides/community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/community.png -------------------------------------------------------------------------------- /src/slides/community0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/community0.png -------------------------------------------------------------------------------- /src/slides/escher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/escher.jpg -------------------------------------------------------------------------------- /src/slides/hor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/hor.png -------------------------------------------------------------------------------- /src/slides/hor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/hor2.png -------------------------------------------------------------------------------- /src/slides/hor3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/hor3.png -------------------------------------------------------------------------------- /src/slides/hor4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/hor4.png -------------------------------------------------------------------------------- /src/slides/hor5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/hor5.png -------------------------------------------------------------------------------- /src/slides/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Center from '../components/Center'; 3 | import Image from '../components/Image'; 4 | 5 | function makeProgression(props, title, items) { 6 | return [ 7 | ...items.map((activeItem, activeIndex) => 8 |
9 |

{title}

10 |
    11 | {items.map((item, i) => 12 |
  • 16 | {item} 17 |
  • 18 | )} 19 |
20 |
21 | ) 22 | ]; 23 | } 24 | 25 | export default function getSlides() { 26 | return [ 27 |
28 | 29 |
, 30 |
31 |

Flux + Elm

32 |
, 33 |
34 |

🎂

35 |
, 36 |
37 | 38 |
, 39 |
40 | 41 |
42 |
43 |

3 Million

44 |
45 |
46 |
, 47 |
48 | 49 |
, 50 |
51 |

What Made Redux Successful?

52 |
, 53 |
54 |

Features and APIs?

55 |
, 56 |
57 |

A change emitter holding a value.

58 |
, 59 |
60 | 61 |
, 62 |
63 | 64 |
, 65 |
66 | 67 |
, 68 |
69 | 70 |
, 71 |
72 | 73 |
, 74 |
75 | 76 |
, 77 |
78 | 79 |
, 80 |
81 | 82 |
, 83 |
84 | 85 |
, 86 |
87 |

Features & APIs?

88 |
, 89 |
90 |

Features & APIs?

91 |
, 92 |
93 | 94 |
, 95 |
96 | 97 |
, 98 |
99 |

Constraints & Contracts

100 |
, 101 |
102 |

Redux Constraints

103 |
    104 |
  • Single State Tree
  • 105 |
  • Actions Describe Updates
  • 106 |
  • Reducers Apply Updates
  • 107 |
108 |
, 109 | ...makeProgression({ alt: true }, 'Debug Workflow', [ 110 | 'Log actions and states', 111 | 'Find the bad state', 112 | 'Check the action', 113 | 'Fix the reducer', 114 | 'Write a test' 115 | ]), 116 | ...makeProgression({ alt: true }, 'Everything Is Data', [ 117 | 'Persistence', 118 | 'Universal rendering', 119 | 'Recording user sessions', 120 | 'Optimistic mutations', 121 | 'Collaborative editing' 122 | ]), 123 |
128 |
140 |

Features

141 |
142 |
154 |

Constraints

155 |
156 |
, 157 |
158 |

Redux Contracts

159 |
    160 |
  • Reducers
  • 161 |
  • Selectors
  • 162 |
  • Middleware
  • 163 |
  • Enhancers
  • 164 |
165 |
, 166 |
167 |

App Contracts

168 |
, 169 |
170 |

Reducers

171 |

(state, action) => state

172 |
, 173 |
174 | 175 |
, 176 |
177 |

Selectors

178 |

(state, ...args) => derivation

179 |
, 180 |
181 | 182 |
, 183 |
184 |

Reducer + Selectors

185 |
, 186 |
187 | 188 |
, 189 |
190 | 191 |
, 192 |
193 |

Ecosystem Contracts

194 |
, 195 |
196 |

Higher Order Reducers

197 |

(reducer, ...args) => reducer

198 |
, 199 |
200 | 201 |
, 202 |
203 | 204 |
, 205 |
206 | 207 |
, 208 |
209 | 210 |
, 211 |
212 | 213 |
, 214 |
215 | 216 |
, 217 |
218 |

♻️

219 |

omnidan/
redux-undo

220 |
, 221 |
222 |

😎

223 |

mattkrick/
redux-optimistic-ui

224 |
, 225 |
226 |

📝

227 |

davidkpiano/
react-redux-form

228 |
, 229 |
230 |

Middleware

231 |

store => next => action => any

232 |
, 233 |
234 | 235 |
, 236 |
237 | 238 |
, 239 |
240 | 241 |
, 242 |
243 | 244 |
, 245 |
246 | 247 |
, 248 |
249 | 250 |
, 251 |
252 | 253 |
, 254 |
255 | 256 |
, 257 |
258 | 259 |
, 260 |
261 | 262 |
, 263 |
264 |

📰

265 |

theaqua/
redux-logger

266 |
, 267 |
268 |

🐥️

269 |

redux-observable/
redux-observable

270 |
, 271 |
272 |

🚀

273 |

apollostack/
apollo-client

274 |
, 275 |
276 |

Enhancers

277 |

278 | createStore => createStore 279 |

280 |
, 281 |
282 |

📦

283 |

tappleby/
redux-batched-subscribe

284 |
, 285 |
286 |

🌈

287 |

raisemarketplace/
redux-loop

288 |
, 289 |
290 |

🔮

291 |

zalmoxisus/
redux-devtools-extension

292 |
, 293 |
298 |
310 |

APIs

311 |
312 |
324 |

Contracts

325 |
326 |
, 327 |
328 |

Lessons Learned

329 |
, 330 |
331 |

Design Holistically

332 |
, 333 |
339 |
351 |

Features

352 |
353 |
365 |

APIs

366 |
367 |
, 368 |
373 |
385 |

Features

386 |
387 |
399 |

Constraints

400 |
401 |
413 |

Contracts

414 |
415 |
427 |

APIs

428 |
429 |
, 430 |
431 |

Make Constraints Useful

432 |
, 433 |
434 |

Make Contracts Social

435 |
, 436 |
437 |

Design the Way Out

438 |
, 439 |
440 | 441 |
, 442 |
443 |

Find the Stress Tests

444 |
, 445 |
446 |

Market Good Ideas

447 |
, 448 |
449 |

Understanding Matters

450 |
, 451 |
452 |

Hype!

453 |
, 454 |
455 |

Real Libraries

456 |
    457 |
  • facebook/relay
  • 458 |
  • reactivex/rxjs
  • 459 |
  • facebook/react
  • 460 |
  • mobxjs/mobx
  • 461 |
  • cerebral/cerebral
  • 462 |
463 |
, 464 |
465 | 466 |
, 467 |
468 | 469 |
, 470 |
471 |

Immutable App
Architecture

472 |

Lee Byron @ Render 2016

473 |
, 474 |
475 |

Cohesive Story

476 |
    477 |
  • Declarative data fetching
  • 478 |
  • Built-in optimistic mutations
  • 479 |
  • Easy-to-test async control flow
  • 480 |
  • Combining local and server data
  • 481 |
  • Great developer experience
  • 482 |
483 |
, 484 |
485 | 486 |
, 487 |
488 | 489 |
, 490 |
491 |

💜

492 |
, 493 |
494 |

P.S.

495 |
, 496 |
497 |

498 | 506 | egghead.io/redux-2 507 | 508 |

509 |
510 | ]; 511 | } 512 | -------------------------------------------------------------------------------- /src/slides/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/logo.png -------------------------------------------------------------------------------- /src/slides/middleware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware.png -------------------------------------------------------------------------------- /src/slides/middleware2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware2.png -------------------------------------------------------------------------------- /src/slides/middleware3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware3.png -------------------------------------------------------------------------------- /src/slides/middleware4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware4.png -------------------------------------------------------------------------------- /src/slides/middleware5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware5.png -------------------------------------------------------------------------------- /src/slides/middleware6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware6.png -------------------------------------------------------------------------------- /src/slides/middleware7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware7.png -------------------------------------------------------------------------------- /src/slides/middleware8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware8.png -------------------------------------------------------------------------------- /src/slides/middleware9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/middleware9.png -------------------------------------------------------------------------------- /src/slides/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/next.png -------------------------------------------------------------------------------- /src/slides/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/npm.png -------------------------------------------------------------------------------- /src/slides/reducer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/reducer.png -------------------------------------------------------------------------------- /src/slides/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { NEXT_SLIDE, PREV_SLIDE } from './actionTypes'; 3 | import getSlides from '../slides/index'; 4 | 5 | const slideCount = getSlides().length; 6 | 7 | function currentSlide(state = 0, action) { 8 | switch (action.type) { 9 | case NEXT_SLIDE: 10 | return Math.min(slideCount - 1, state + 1); 11 | case PREV_SLIDE: 12 | return Math.max(0, state - 1); 13 | default: 14 | return state; 15 | } 16 | } 17 | 18 | export default combineReducers({ 19 | currentSlide 20 | }); -------------------------------------------------------------------------------- /src/slides/selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/selector.png -------------------------------------------------------------------------------- /src/slides/setstate2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/setstate2.png -------------------------------------------------------------------------------- /src/slides/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store.png -------------------------------------------------------------------------------- /src/slides/store2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store2.png -------------------------------------------------------------------------------- /src/slides/store3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store3.png -------------------------------------------------------------------------------- /src/slides/store4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store4.png -------------------------------------------------------------------------------- /src/slides/store5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store5.png -------------------------------------------------------------------------------- /src/slides/store6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store6.png -------------------------------------------------------------------------------- /src/slides/store7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store7.png -------------------------------------------------------------------------------- /src/slides/store8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/store8.png -------------------------------------------------------------------------------- /src/slides/tom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/tom.jpg -------------------------------------------------------------------------------- /src/slides/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaearon/the-redux-journey/9c04b2f4baa7e5c1863f0620c3ba31085a991aa6/src/slides/twitter.png -------------------------------------------------------------------------------- /src/theme.less: -------------------------------------------------------------------------------- 1 | // Default colors 2 | @base00: #181818; 3 | @base01: #282828; 4 | @base02: #383838; 5 | @base03: #585858; 6 | @base04: #b8b8b8; 7 | @base05: #d8d8d8; 8 | @base06: #e8e8e8; 9 | @base07: #f8f8f8; 10 | @base08: #ab4642; 11 | @base09: #dc9656; 12 | @base0A: #f7ca88; 13 | @base0B: #a1b56c; 14 | @base0C: #86c1b9; 15 | @base0D: #7cafc2; 16 | @base0E: #ba8baf; 17 | @base0F: #a16946; 18 | 19 | @font-main: 'Lato', 'LatoBlack', sans-serif; 20 | @font-code: 'Operator Mono', Consolas, monospace; 21 | @fontsize-root: 3.25rem; 22 | 23 | @keyframes shake { 24 | 0%, 100% {transform: translateX(0);} 25 | 20%, 80% {transform: translateX(-0.05rem);} 26 | 40%, 80% {transform: translateX(1.75rem);} 27 | } 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | entry: [ 7 | 'webpack-dev-server/client?http://localhost:3000', 8 | 'webpack/hot/only-dev-server', 9 | './src/index' 10 | ], 11 | output: { 12 | path: path.join(__dirname, 'build'), 13 | filename: 'bundle.js', 14 | publicPath: '/' 15 | }, 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin({ multiStep: true }), 18 | new webpack.NoErrorsPlugin() 19 | ], 20 | resolve: { 21 | extensions: ['', '.js', '.jsx'] 22 | }, 23 | module: { 24 | loaders: [{ 25 | test: /\.jsx?$/, 26 | loaders: ['react-hot', 'babel'], 27 | include: path.join(__dirname, 'src') 28 | }, { 29 | test: /\.css$/, 30 | loaders: ['style', 'css'], 31 | include: path.join(__dirname, 'src') 32 | }, { 33 | test: /\.less$/, 34 | loaders: ['style', 'css', 'less'], 35 | include: path.join(__dirname, 'src') 36 | }, { 37 | test: /\.jpg|png|svg|ico$/, 38 | loaders: ['file-loader'], 39 | include: path.join(__dirname, 'src') 40 | }] 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | './src/index' 7 | ], 8 | output: { 9 | path: path.join(__dirname, 'build'), 10 | filename: 'bundle.js', 11 | publicPath: '' 12 | }, 13 | plugins: [ 14 | new webpack.DefinePlugin({ 15 | 'process.env.NODE_ENV': '"production"' 16 | }), 17 | new webpack.optimize.UglifyJsPlugin() 18 | ], 19 | resolve: { 20 | extensions: ['', '.js', '.jsx'] 21 | }, 22 | module: { 23 | loaders: [{ 24 | test: /\.jsx?$/, 25 | loaders: ['babel'], 26 | include: path.join(__dirname, 'src') 27 | }, { 28 | test: /\.css$/, 29 | loaders: ['style', 'css'], 30 | include: path.join(__dirname, 'src') 31 | }, { 32 | test: /\.less$/, 33 | loaders: ['style', 'css', 'less'], 34 | include: path.join(__dirname, 'src') 35 | }, { 36 | test: /\.jpg|png|svg|ico$/, 37 | loaders: ['file-loader?name=[name].[ext]', 'image-webpack-loader'], 38 | include: path.join(__dirname, 'src') 39 | }] 40 | } 41 | }; 42 | --------------------------------------------------------------------------------