├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── build └── bundle.js ├── index.html ├── notes ├── 0-what-is-react.md ├── 0-what-is-react.pdf ├── 1-setup.md ├── 2-firstComponent.md ├── 3-componentMethodsAndFunctionalComponents.md ├── 4-addingFunctionality.md ├── 5-settingUpRedux.md ├── 6-reduxActionsAndReducers.md └── react-lifecycle.png ├── package.json ├── src └── app.js ├── talking-points.md └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cody Barrus 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setup (Before Class) 2 | 3 | ## Make sure you have the following tools: 4 | - A Mac computer (Linux and Windows welcome, but I'll be able to offer very limited support if any issues pop up, especially with Windows) 5 | - An IDE (Sublime, Atom, VIM or something similar) 6 | - Google Chrome or FireFox 7 | - Node.js installed 8 | 9 | ## Steps to take before class 10 | 11 | ### If you don't have Node.js installed 12 | If you've already installed node, you can skip this step. 13 | 14 | - Visit https://nodejs.org/en/ and download node 4.2.4 (or the latest version of 4.x.x). Don't download version 5.x.x unless you know what you're doing. 15 | - Open the installer and follow the instructions. 16 | 17 | ### Once node is installed 18 | 19 | 1. Clone this repo, then in terminal enter the following commands 20 | 2. `git clone git@github.com:goopscoop/ga-react-tutorial.git` 21 | 3. `cd ga-react-tutorial` 22 | 4. `npm install` 23 | 5. `npm start` 24 | 6. In your web browser, enter the url `http://localhost:8080/webpack-dev-server/#/`. 25 | - You should see a bar at the top of the page that says "App ready." Below that, it'll say "Hello General Assembly. Welcome to React". If you see this, you're ready for class. 26 | - If you don't see this, please let me know at the beginning of class. 27 | - If you feel adventurous, go ahead and poke around the files. Most of what you'll find is general boilerplate that will make your dev experiance nicer. 28 | 29 | # Continued Ed Links 30 | 31 | ## React 32 | - https://github.com/facebook/react/wiki/Sites-Using-React 33 | - https://facebook.github.io/react/docs/thinking-in-react.html 34 | - https://egghead.io/technologies/react 35 | - https://github.com/reactjs/react-basic 36 | 37 | ## Virtual DOM 38 | - http://calendar.perfplanet.com/2013/diff/ 39 | - http://conferences.oreilly.com/fluent/fluent2014/public/schedule/detail/32395 40 | - http://stackoverflow.com/questions/21109361/why-is-reacts-concept-of-virtual-dom-said-to-be-more-performant-than-dirty-mode 41 | - https://github.com/Matt-Esch/virtual-dom 42 | 43 | ## JSX 44 | 45 | JSX is the HTML looking code that you'll find within the render method (and sprinkled throughout other custom methods) in React. While it looks like HTML, it's actually just JavaScript. 46 | 47 | *Example* 48 | 49 | ``` 50 | // JSX 51 |

Hello, world! I am an H1.

52 | 53 | // Compiles to this 54 | React.createElement('h1', {className: "heading"}, 55 | "Hello, world! I am an H1." 56 | ) 57 | ``` 58 | 59 | - https://facebook.github.io/react/docs/jsx-in-depth.html 60 | - https://facebook.github.io/react/jsx-compiler.html 61 | - http://babeljs.io/docs/usage/transformers/other/react/ 62 | 63 | ## Flux (old) and Redux (new) - (Advanced Data Handling) 64 | 65 | ###Flux 66 | 67 | Flux is a data handling methodology created by Facebook for React. One of the best implementations of Flux come in the form of Flummox. However, the React community is starting to move away from Flux towards a new data handling methodology called Redux. 68 | 69 | Learn more about Flux: 70 | - https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6#.ewluyug9y 71 | - http://acdlite.github.io/flummox 72 | 73 | ### Redux 74 | 75 | Many Flux implementations are slowly being abandoned for the new Redux methodology. In fact, the creator of Flummox has joined the Redux team. This is new tech and not as widely documented as Flux, so it has a steeper learning curve. However, it appears to be the way data handling is headed for React (as well as many other frameworks). 76 | 77 | Learn more about Redux 78 | - https://code-cartoons.com/a-cartoon-guide-to-flux-6157355ab207#.wz28eywrv 79 | - https://github.com/rackt/redux 80 | - https://egghead.io/series/getting-started-with-redux 81 | - https://www.youtube.com/watch?v=xsSnOQynTHs 82 | - https://github.com/erikras/ducks-modular-redux 83 | - Ducks - https://github.com/erikras/ducks-modular-redux 84 | 85 | ## ES6 (ES2015) 86 | 87 | ES6 aka ES2015 is the newest itteration of JavaScript that comes packaged with a lot of handy new features. Some of my favorites include new variable types (`let` and `const`), arrow functions `() => {}`, spread opperators `{...props}`, destructuring `cosnt {thing1, thing2, thing3} = object`, and a bunch of other useful features. Well worth looking into. 88 | 89 | - http://babeljs.io/docs/learn-es2015/ 90 | - https://babeljs.io/ - transpiler 91 | 92 | ## React Router (may not cover in class) 93 | 94 | React Router is a package used by most React Single Page Apps. It's easy to use and written in JSX syntax. 95 | 96 | - https://github.com/rackt/react-router 97 | 98 | Here's an example of how you would set up routes with the completed app we build in the workshop: 99 | 100 | ```javascript 101 | import { Router, Route, hashHistory } from 'react-router' 102 | 103 | 104 | const store = configureStore(); 105 | 106 | class App extends React.Component { 107 | render(){ 108 | return( 109 | 110 | 111 | 112 | 113 | 114 | 115 | ); 116 | } 117 | } 118 | 119 | ReactDOM.render(, document.getElementById('app')); 120 | ``` 121 | 122 | ## Webpack 123 | 124 | Webpack is a very complicated but useful bundler. It allows for things like hot reloading, `import`, `export`, etc. Webpack is set up by a single `webpack.config.js` file/ 125 | 126 | - https://webpack.github.io/ 127 | 128 | ## Other 129 | 130 | - Fetch - https://github.com/github/fetch 131 | - Test w/ Unexpected - http://unexpected.js.org/ 132 | - Test w/ Webpack, Karma & Expect - https://medium.com/@scbarrus/how-to-get-test-coverage-on-react-with-karma-babel-and-webpack-c9273d805063#.uvl9e6hm4 133 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /notes/0-what-is-react.md: -------------------------------------------------------------------------------- 1 | 2 | # Welcome to React!!! 3 | 4 | ## Teacher: Cody Barrus 5 | 6 | - Twitter: @SCBarrus 7 | - Medium: @SCBarrus 8 | - Github: goopscoop 9 | 10 | Before class, go here: https://github.com/goopscoop/ga-react-tutorial 11 | 12 | --- 13 | 14 | # React 15 | ## What is it? 16 | 17 | --- 18 | 19 | # Library or Framework 20 | 21 | --- 22 | 23 | # An Ecosystem 24 | ## By itself it's a Library 25 | ## With Webpack, Babel and Redux it's a Framework 26 | 27 | --- 28 | 29 | # React is the View Layer 30 | 31 | --- 32 | 33 | # Webpack is the Bundler 34 | 35 | --- 36 | 37 | # Babel is the Transpiler 38 | 39 | --- 40 | 41 | # Redux is the Data Layer 42 | 43 | --- 44 | 45 | # Features of React 46 | 47 | --- 48 | 49 | ## Virtual DOM 50 | 51 | --- 52 | 53 | ## JSX 54 | 55 | ```JavaScript 56 | // JSX 57 |

Hello, world! I am an H1.

58 | 59 | // Compiles to this 60 | React.createElement('h1', {className: "heading"}, 61 | "Hello, world! I am an H1." 62 | ) 63 | ``` 64 | 65 | --- 66 | 67 | ## Components 68 | 69 | - `class` components 70 | - Stateless components 71 | 72 | --- 73 | 74 | ## Lifecycle Methods 75 | 76 | ![left](./react-lifecycle.png) 77 | 78 | --- 79 | 80 | ## Uni-directional Data 81 | 82 | --- 83 | 84 | ## React Native 85 | ## Isomorphic Rendering 86 | 87 | --- 88 | -------------------------------------------------------------------------------- /notes/0-what-is-react.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goopscoop/ga-react-tutorial/4e0856aa90af79faedff5f98cc583380352ee3f2/notes/0-what-is-react.pdf -------------------------------------------------------------------------------- /notes/1-setup.md: -------------------------------------------------------------------------------- 1 | # Setting up your React App - The most difficult part of any react app :) 2 | 3 | ## 1. NPM INIT 4 | 5 | `npm init` to create your `package.json` file. 6 | 7 | ## 2. Install dependencies: 8 | - `npm install --save react` - Install React. 9 | - `npm install --save react-dom` Install React Dom, the package that handles the virtual DOM. 10 | - `npm install --save-dev webpack` - Install Webpack, our module bundler. 11 | - `npm install --save-dev webpack-dev-server` - A dev server that enables hot reloading. 12 | - `npm install --save-dev babel` - Install Babel, which will transpile our ES6 code into ES5. 13 | - install these other babel dependencies: 14 | - `npm install --save-dev babel-core` - The main features of babel. 15 | - `npm install --save-dev babel-polyfill` - Babel includes a polyfill that includes a custom regenerator runtime and core.js. This will emulate a full ES6 environment. 16 | - `npm install --save-dev babel-loader` - This package allows transpiling JavaScript files using Babel and webpack. 17 | - `npm install --save-dev babel-runtime` - A dependency of Babel transform runtime plugin. 18 | - `npm install --save-dev babel-plugin-transform-runtime` - Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals. 19 | - `npm install --save-dev babel-preset-es2015` - Babel preset for all es2015 plugins. 20 | - `npm install --save-dev babel-preset-react` - Strip flow types and transform JSX into createElement calls. 21 | - `npm install --save-dev babel-preset-stage-0` - All you need to use stage 0 (and greater) plugins (experimental javascript). 22 | 23 | ## 3. Open `package.json` and add some scripts: 24 | ``` 25 | "scripts": { 26 | "start": "webpack-dev-server --hot --inline --progress --colors", 27 | "build": "webpack --progress --colors" 28 | } 29 | ``` 30 | 31 | Running `npm start` will start your dev server. 32 | Running `npm build` will build your app for production. 33 | 34 | ## 4. Setup Webpack 35 | Webpack is our bundler. It's an important peice of our dev environment, and alows us to use some awesome features like hot reloading. Our `webpack.config.js` file is below with comments. As your app grows, your config file may change. 36 | 37 | ```javascript 38 | var webpack = require('webpack'); 39 | module.exports = { 40 | entry: [ 41 | 'babel-polyfill', 42 | 'webpack/hot/only-dev-server', 43 | './src/app.js' // the entry point of our app. All react apps have one parent component. Yours should live here. 44 | ], 45 | output: { 46 | path: __dirname + '/build', // When we run 'npm build', our bundled .js file will be exported here. 47 | filename: "bundle.js" // Our exported file will be called 'bundle.js' 48 | }, 49 | module: { 50 | loaders: [ 51 | { 52 | test: /\.js$/, // all .js files will be subjected to these loaders 53 | exclude: /node_modules/, // no files within the node_modules folder will be processed 54 | loader: 'babel-loader', // all .js files will be ran through babel for transpiling 55 | query: { 56 | plugins: ['transform-runtime'], 57 | presets:['es2015', 'react', 'stage-0'] // we are using es2015, plus reacts jsx, plus some experimental javascript 58 | } 59 | }, 60 | { test: /\.css$/, loader: "style!css" } // allows us to import css files in our javascript. 61 | ] 62 | } 63 | }; 64 | 65 | ``` 66 | Your boiler plate is now done. Time to start writing some react code. 67 | -------------------------------------------------------------------------------- /notes/2-firstComponent.md: -------------------------------------------------------------------------------- 1 | # React Basics - Your First Component 2 | 3 | Now that we have our app boilerplate out of the way, it's time to get to the fun stuff. Let's set up our first component. 4 | 5 | ## 1. index.html 6 | 7 | The first thing we need to do is create out `index.html` page in the root of our project. This will be a very basic page. For this project, I've linked to a bootstrap cdn for basic styling. The only other things to look out for is the `
` tag (this is where our app will be injected), and the `` tag (which will run the javascript that powers our app). 8 | 9 | Here's the full HTML: 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | Document 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | ``` 25 | ## 2. Folders & Files 26 | 27 | Create a new folder called `src`. The majority of your app's code will live inside this folder from now on. 28 | 29 | Within `src` create a `app.js` file. Within every React app there is one root component, and for us, this is it. All other components will be nested within this one. 30 | 31 | ## 3. Imports 32 | 33 | First we need to import react into this file. To do this, we'll be using a little ES6. `import React from 'react';` The `import` functionality is an ES6 feature, but currently it's powered by Webpack (as opposed to the browser where it may be powered in the near future). 34 | 35 | We also need to import ReactDOM. Because react is used not only to power web apps (with ReactDOM), but also native mobile apps (with React Native), the rendering process is handled by ReactDOM. This is where the vertual DOM is created and diff'd against the actual DOM. 36 | 37 | To import ReactDOM, add `import ReactDOM from 'react-dom';` at the top of the file. 38 | 39 | ## 4. Our First Component 40 | 41 | Now we need to create our first Component. To do this we create an ES6 class that extends `React.Component`. At this point you may be wondering what is enabling all of this ES6 to work. While a lot of it is actually available in your browser right now, we are using Babel to power stage-0 and up features as well as make all ES6 backwards compatable. 42 | 43 | To create your first class: 44 | 45 | ```javascipt 46 | class App extends React.Component { 47 | render(){ // Every react component has a render method. 48 | return( // Every render method returns jsx. Jsx looks like HTML, but it's actually javascript and functions a lot like xml, with self closing tags requiring the `/` within the tag in order to work propperly 49 |
50 | Hello World 51 |
52 | ); 53 | } 54 | } 55 | ``` 56 | 57 | Notice that "Hello World" is nested within `div`s. All jsx must be nested within parent `div`s or it wont work. 58 | 59 | ## 5. Rendering 60 | 61 | Finally, we need to render this component to the DOM. To do this, we'll use the `ReactDOM.render` method. 62 | 63 | At the bottom of your `App.js` file, add: `ReactDOM.render(, document.getElementById('app'));` 64 | 65 | The first parameter is our App, which is rendered as ``. The second is the DOM element where we'll be injecting our app. In this case, a `div` tag with the id of "app". -------------------------------------------------------------------------------- /notes/3-componentMethodsAndFunctionalComponents.md: -------------------------------------------------------------------------------- 1 | Ok, now we have our root component in the `app.js` file. It doesn't do much on it's own, so we're going to start making this app do stuff. 2 | 3 | The app we're making is a to do app. We'll be building an app with an input field that takes in to do items and adds them to a list. Once the item is clicked, we'll cross the item off the list. When a delete button is cliced, the item will be removed. Through this process, you'll learn all the basic building blocks of a fully funcitonal React app. 4 | 5 | But before we do all of this, we need to start with some smaller componets. A component is the building block of a react app. There are two types of components, class based components and functional componets. In this stage, we'll make both a class component and a functional component, use lifecycle methods, work with state, and pass props. 6 | 7 | ## 1. Components Folder 8 | 9 | Start by creating a `components` folder within `src`. So your file structure should look like `~/src/components`. 10 | 11 | ## 2. ToDoApp.js 12 | 13 | Inside `components`, we're going to create the file `ToDoApp.js`. As with all react components, we'll start by importing react with `import React from 'react';` 14 | 15 | ## 3. Test Component 16 | 17 | This will be a class component. As we saw before, all class components have a render method which returns jsx. This one will be no different. 18 | 19 | Your ToDoApp class should look like this: 20 | 21 | ```javascript 22 | class ToDoApp extends React.Component { 23 | render() { 24 | return ( 25 |
To Do App
26 | ); 27 | } 28 | } 29 | ``` 30 | 31 | ## 4. Export Component 32 | 33 | In order to import this component into our app, first we'll need to export it. At the bottom of the file add `export default ToDoApp;` 34 | 35 | ## 5. Import Component 36 | 37 | In `app.js`, at the top of the file, add `import ToDoApp from '.components/ToDoApp';` Then replace the text `Hello World` with our new component. We can now render our `ToDoApp` component by adding it to our JSX where our Hello World text was. To do this, add `` You'll notice that this is a self closing tag. 38 | 39 | ## 6. Smart Component 40 | 41 | In your browser, you should now see the words "To Do App" has replaced "Hello World"! Nice, you've now nested your first child component inside of your apps Root component. This is the general pattern you'll use when building any React app. So, now let's make this app smart. 42 | 43 | Back in `ToDoApp` we're going to start making our first list. First, let's make it look half decent with a little bootstrap. Copy the following JSX into the `return` statment of the `render` method, replacing all of the current jsx: 44 | 45 | ```html 46 |
47 |
48 |
49 |
50 |

My To Do App

51 |
52 | List goes here. 53 |
54 |
55 |
56 |
57 | ``` 58 | 59 | Now if you look at your browser, the text should read "My To Do App" followed by "List Goes Here" within a bootstrap panel. So where are we going to store the data that populates our list? The answer is `state`. Every class based component has `state` which can be accessed anywhere in your component with `this.state` and can be update with `this.setState({ key: "value" })`. While we try not to use component state unless it's absolutely necessary, for the sake of this example, and for the immediate future, we will be using state only in this component until we setup redux. 60 | 61 | Inside `ToDoApp` we also have access to lifecycle methods, one of which is `componentWillMount`. This method is run once when the page loads and before the render method is called, so if you need to access data from a server or API, this is a good place to do that. In our case, we're going to start with a dummy list. 62 | 63 | Inside `ToDoApp`, before the `render` method, add: 64 | 65 | ```javascript 66 | componentWillMount(){ // run before the render method 67 | this.setState({ // add an array of strings to state. 68 | list: ['thing1', 'thing2', 'thing3'] 69 | }) 70 | }; 71 | ``` 72 | 73 | And now we have access to a dummy list. It's important to note that React depends on state, as well as props (which we'll cover soon). Only when state or props change will the react app refresh. This means if data changes somewhere else, and is used in the view, the view won't update to display the change. Only when state or props change. 74 | 75 | ## 7. A List 76 | 77 | Now we need to add our list to the view. Instead of simply adding the JSX into our `render` method, we'll be creating a functional component to handle the list. Once again, we'll be following the pattern of nesting components within components. Functional components are new to React, and it is considered a best practice to use them whenever possible. However, it's important to note that functional components dont have lifecycle methods or state. They are simply a function that returns jsx and contain props as an argument. 78 | 79 | We've talked about props a bit now. Props are the name of data that is passed down from a parent component into a child component. This is typically the way data is passed through a react app. Keeping the data near the top of the app, and letting that data trickle down through components keeps your app running proficiantly. Poor handling of data and props can slow your app down, but if you follow the practices within this course you're app will run very fast. 80 | 81 | To create our functional component, first create a new file in `components` called `List.js`. Start by importing react, then we'll create a function that returns JSX. Our function will be a `const` because it wont be manipulated over time. It'll take one argument called `props`. 82 | 83 | Your function should look like this: 84 | 85 | ```javascript 86 | const List = (props) => { // we're using an arrow function and const variable type, a ES6 features 87 | 88 | return ( 89 |
90 | I'm a list!!! 91 |
92 | ) 93 | }; 94 | 95 | export default List; 96 | ``` 97 | 98 | ## 8. Dynamic List 99 | 100 | Inside `ToDoApp.js`, import List. Then replace the words `List goes here.` with the `List` component. It will look just like a class component in our JSX: ``. Now in the browser you should see the words "I'm a list!!!" 101 | 102 | Now, to make our list, we need to give `List` some data, which we'll pass through props. We'll call this prop "listItems" which will read from state. We do this by adding what will look very similar to an HTML attribute to our JSX `List` component call. It will look like this: `` Now `List` has access to the data which is containe in `ToDoApp` through props! 103 | 104 | To access this data, in the `List` component we'll need to render the list. Replace the JSX within the `return` statement with the following code: 105 | 106 | ```javascript 107 |
108 |
    109 | { 110 | list // this is a variable we'll define next 111 | } 112 |
113 |
114 | ``` 115 | 116 | Notice the curly braces. Within these sections, javascript can execute and what is returned will be added to the view. In this case, the result of a yet to be defined list variable will be added to the view. So let's define that variable: 117 | 118 | ```javascript 119 | const list = props.listItems.map((el, i)=>( 120 | // All where doing here is getting the items listItems prop 121 | // (which is stored in the state of the parent component) 122 | // which is an array, and we're running the .map method 123 | // which returns a new array of list items. The key attribute is 124 | // required, and must be unique. 125 |
  • {el}
  • 126 | )); 127 | ``` 128 | 129 | The entire component should look like: 130 | 131 | ```javascript 132 | import React from 'react'; 133 | 134 | const List = (props) => { 135 | 136 | const list = props.listItems.map((el, i)=>( 137 |
  • {el}
  • 138 | )); 139 | 140 | return ( 141 |
    142 |
      143 | { 144 | list 145 | } 146 |
    147 |
    148 | ) 149 | }; 150 | 151 | export default List; 152 | ``` 153 | Now if we look at the browser, we should see a list of items! Next we'll need to add some functionality to our app. 154 | -------------------------------------------------------------------------------- /notes/4-addingFunctionality.md: -------------------------------------------------------------------------------- 1 | Now that we have something showing up in our view, it's time to add some functionality to our app. Because this is a to do app, we'll need to add a way for new items to be added to the list, a way to make off items as completed, and a way to delete any items we no longer want. 2 | 3 | ## 1. Functional Component 4 | 5 | First we need to add an input element so the user can add a todo item. Let's do this by creating a new component. In the `components` folder, create `Input.js`, and within create and export a functional component called Input. 6 | 7 | Remember that functional components return JSX and have access to the props argument. For now, we just need to return the form. Paste the following JSX into the return statement of your functional component: 8 | 9 | ```html 10 |
    11 |
    13 | 17 | 23 | 27 |
    28 |
    29 | ``` 30 | 31 | ## 2. Input 32 | 33 | Right now our JSX isn't doing anything special, just adding basic HTML to the view. Let's test it out to make sure everything is hunky dorey by importing the `Input` component into `ToDoApp.js` and instanciating it below the `List` component. It should look like this ``. 34 | 35 | Now's the time when I'll start keeping some of my instructions that we've already covered in past sections a little more vague. If you ever find yourself unable to figure things out, remember that you can reference the source code in this repo at any time. 36 | 37 | Ok, let's check the view. Everything there? Good! Let's make it do stuff. 38 | 39 | ## 3. Props 40 | 41 | The first thing we need to do is give ourselves access to the value of the input field. This value will need to be accessed in other components, so we don't want to handle the data storage within our `Input` component. In fact, I'll just reiterate that it's never a good idea to store data in a child component whenever possible. Most of the time, data should be stored at the top of your app and trickle down components through props. 42 | 43 | Another thing to remember is that even though we are currently storing state in our `ToDoApp` component, eventually we'll be adding Redux to handle data for our entire app. We are simply working with local State to display the many things you can do in a React component. 44 | 45 | Ok, now that we've got that out of the way, we're going to store the value of the input within the state of `ToDoApp` (later to be handled by Redux). To begin, within the `componentWillMount` method, add `newToDo: ''` within the `this.setState` call. It should look like this: 46 | 47 | ```javascript 48 | componentWillMount(){ 49 | this.setState({ 50 | list: ['thing1', 'thing2', 'thing3'], 51 | newToDo: 'test' 52 | }) 53 | }; 54 | ``` 55 | 56 | Now we need to pass that value through props. Find the `` and add a new prop called value. Can you remember how to access a value from state? Pass it in. Look to the `listItems` prop on the `List` component for a hint. 57 | 58 | ## 4. Destructuring 59 | 60 | In `Input.js` we now have access to the value prop, but we're going to be handling props in a slightly different way this time around. In this case we'll be adding a new ES6 feature called "destructuring" to make dealing with props just a little nicer. 61 | 62 | Instead of adding the `props` argument to the `Input` component, we're going to add this `({ value })`. What this does is take the `props` argument, and breaks it down into variables we can have access to based on the key value pairs within the props object. 63 | 64 | For example, this: 65 | 66 | ``` 67 | var props = { 68 | name: 'hector', 69 | age: 21 70 | } 71 | 72 | 73 | function log(props){ 74 | console.log(props.name); 75 | console.log(props.age); 76 | } 77 | 78 | log(props); 79 | ``` 80 | 81 | is the same as this: 82 | 83 | ``` 84 | let props = { 85 | name: 'hector', 86 | age: 21 87 | } 88 | 89 | log = ({name, age}) => { 90 | console.log(name); 91 | console.log(age); 92 | } 93 | 94 | log(props); 95 | ``` 96 | 97 | So let's give our input access to the value. In the `input` within our JSX, add `value={value}`. Check the browser to confirm that this is indeed the value. 98 | 99 | ## 5. setState 100 | 101 | You might have noticed that now that we've declared the value, we can't change it. That's because the value is being held in state and we haven't created a method to update state with our new value based on user input. 102 | 103 | To do this, we'll need to add an `onChange` method to the same `input` we added value. Under value add 'onChange={onChange}', then where we destructure the props, add `onChange` so it looks like `({ onChange, value })` 104 | 105 | Next, go within `ToDoApp.js`. Under `componentWillMount` add a new custom method called `onInputChange`. This will be a simple method that updates the state whenever the user adds or deletes something from the input. It'll take a single argument, `event` where it will capture the user input from. Within the method, add `this.setState({ newToDo: e.target.value });` 106 | 107 | The method should look like this: 108 | 109 | ```javascript 110 | onInputChange = (event) => { 111 | this.setState({ newToDo: event.target.value}); // updates state to new value when user changes the input value 112 | }; 113 | ``` 114 | 115 | Now, we just need to give our custom `Input` component access to this method through props. Within the JSX, add `onChange={this.onInputChange}`. That's everything we need to make the input editable, as well as give our app access to it's value at any time. 116 | 117 | ## 6. Adding items 118 | 119 | Now time to add new items to our list. To do this, we're going to create another custom method within `ToDoApp` called `onInputSubmit`. This also contains an `event` arugment. Within the body of the method, we're going to `event.preventDefault()`, then use the `setState` method to push a new item to the list array. However, it's important to note that state should never be modified directly, otherwise React may not render the change, or state may be overridden unexpectedly. 120 | 121 | To get around this, `this.setState` also can take a callback function with access to `previousState`. To take advantage of this, we could write this (but don't): 122 | 123 | ```javascript 124 | this.state((previousState)=>({ 125 | list: previousState.list.push(previousState.newToDo) 126 | })) 127 | ``` 128 | 129 | Above you can see how we use the `previousState` to update existing state. This method will work fine, but when we move on to Redux, our data handling layer, the `push` method will no longer be a viable option. This is because Redux depends on state being immutable. That means we can never change state directly, and unfortunately, push modifies the array it's called upon. 130 | 131 | To get around this, and to make our development easier when it comes to implementing Redux, ES6 has some handy new features we're going to use. In this case, we're going to use an array spread opperator. What this does is return a new array by itterating through the old array and leaving the old array in tact. Then we'll simply add our item into the end of an array literal. It'll look like this: 132 | 133 | ```javascript 134 | this.setState((previousState)=>({ 135 | list: [...previousState.list, previousState.newToDo ], // the spread opperator is called by using the ... preceding the array 136 | })); 137 | ``` 138 | 139 | Now that we have that taken care of, there's one more step we need to take. When a new list item is submitted, we need to reset the `newToDo` value to ''. To do this, simply add `newToDo: ''` within the `setState` method. It'll look like this: 140 | 141 | ```javascript 142 | this.setState((previousState)=>({ 143 | list: [...previousState.list, previousState.newToDo ], 144 | newToDo: '' 145 | })); 146 | ``` 147 | 148 | Now that the method is done, see if you can pass it to the `Input` component, and add it to the `onSubmit` method on the `form` tag within the JSX. If you can't figure it out, take a look at the source code. 149 | 150 | ## 7. Cross off items 151 | 152 | It's time to add the ability to cross off list items. To do this, we'll need to change the way we store our listItems. Instead of storing an array of strings, we'll store an array of objects with an `item` key which will hold a string of the item name, and a `done` boolean. 153 | 154 | Now that we've made this discovery, it's time to update our data accordingly. For now, go to the `componentWillMount` method, and remove the strings from the list key. They've served their purpose, we can work without them from now on. It should look like: `list: []`. Then go to `onInputSubmit` and change the `setState` method to account for the new change in data structure. It'll look like this: 155 | 156 | ```javascript 157 | onInputSubmit = (event) => { 158 | event.preventDefault(); 159 | this.setState((previousState)=>({ 160 | list: [...previousState.list, {item: previousState.newToDo, done: false }], // notice the change here 161 | newToDo: '' 162 | })); 163 | }; 164 | ``` 165 | 166 | With that change in place, we need a method to change the `done` boolean in each list item without modifying existing state. Try to figure it out without looking at the snippet below. It'll look like: 167 | 168 | ```javascript 169 | onListItemClick = (i) => { // takes the index of the element to be updated 170 | this.setState((previousState)=>({ 171 | list: [ 172 | ...previousState.list.slice(0, i), // slice returns a new array without modifying the existing array. Takes everything up to, but not including, the index passed in. 173 | Object.assign({}, previousState.list[i], {done: !previousState.list[i].done}), // Object.assign is a new ES6 feature that creates a new object based on the first param (in this case an empty object). Other objects can be passed in and will be added to the first object without being modified. 174 | ...previousState.list.slice(i+1) // takes everything after the index passed in and adds it to the array. 175 | ] 176 | })) 177 | }; 178 | ``` 179 | 180 | With this method ready to role, let's pass it down to the `List` component through a prop called `onClick`. We'll need to make some changes to how this component displays it's list to make this work. There is one gotcha here, we need to pass in the index of the element clicked, but we can't call the function here otherwise our app won't function properly. To get around this we'll use the `.bind()` method with the first parameter being `null` and the second being the index. I'll look like this: 181 | 182 | ```javascript 183 | onClick={props.onClick.bind(null, i)} 184 | ``` 185 | 186 | First, add an `onClick` method to the JSX `span` wrapping `{el}` and pass in the `onClick` prop we've given `List`. Let's also change `{el}` to `{el.item}` while we're here, to handle the new data. 187 | 188 | Next, we're going to give this `span` some style. Within a `style` attribute, we're going to check `el.done`, and if it's `true` return `{textDecoration: 'line-through', fontSize: '20px'}`, and if false return `{textDecoration: 'none', fontSize: '20px'}`. I like doing this with a turnery. It'll look like this: 189 | 190 | ```javascript 191 | 199 | ``` 200 | 201 | Now, when we click on the element, it'll be crossed out. Cool! Try adding some items and crossing them out in your browser. 202 | 203 | ## 8. Delete Items 204 | 205 | Finally, we need to be able to delete list items. This will be a very similar action to crossing list items off. In fact, it'll be nearly identical once we've added a delete button. Try figuring it out, and if you can't, come back and follow along. 206 | 207 | First we need a delete button. Add it after the list item like so: 208 | 209 | ```javascript 210 | 215 | ``` 216 | 217 | Now we need to give it an onClick method. Let's create that method first within the `ToDoApp` component. This method will take the index and update state to contain all list items except the on deleted. It'll look like this: 218 | 219 | ```javascript 220 | deleteListItem = (i) => { 221 | this.setState((previousState)=>({ // using previous state again 222 | list: [ 223 | ...previousState.list.slice(0, i), // again with the slice method 224 | ...previousState.list.slice(i+1) // the only diffence here is we're leaving out the clicked element 225 | ] 226 | })) 227 | }; 228 | ``` 229 | 230 | Pass the `deleteListItem` method to list, then add it to the delete button. Remeber to use `.bind`. Check the source files if you're having troubling with this last step. 231 | 232 | Now we have an app with some functionality. It's pretty nice, but this method of handling data won't scale to larger apps, so next we'll learn how to convert this app to use Redux! 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /notes/5-settingUpRedux.md: -------------------------------------------------------------------------------- 1 | So far we've learned how to setup our app with Webpack and Babel, how to create class based and functional components, handling state within a component, and adding functionality. While this alone is sufficent to power a small app like ours, as our app grows, we'll quickly find that handling data and actions in this way is insufficient. That's where Redux comes in. 2 | 3 | So how does Redux handle data differently? First, Redux gives your app a single state object for your entire app. This is in contrast to other data handling methods like Flux which often will contain multiple states based on the view. So how does Redux deal with a complex app where a single state object may get too complex? It breaks data handling into seperate reducer functions, action creators, and actions that work together to make data handling streamlined. 4 | 5 | To learn more about Redux, look at the readme at the root of this project where you'll find many informative articles that delve in depth on the subject, including some that compair redux with flux. 6 | 7 | Ok, let's get started. 8 | 9 | ## 1. First we'll need to install some new packages. 10 | 11 | First, let's install `redux` and `react-redux` (which has some bindings for pairing redux with react). 12 | 13 | ``` 14 | npm install --save redux 15 | npm install --save react-redux 16 | ``` 17 | 18 | Next, we're going to install a piece of redux middleware called `redux-logger` which will help us while we develop. 19 | 20 | ``` 21 | npm install --save redux-logger 22 | ``` 23 | 24 | When you begin working on your own apps, you might find other middleware useful as well, including `redux-thunk` and `redux-promise`, but for the sake of this project, we're going to be sticking to the logger middleware only. 25 | 26 | ## 2. Setup 27 | 28 | Now we need to setup our app to handle Redux. There's a little boilerplating involved that you just need to do every time you fit an app with redux, so we're going to keep it simple this time. 29 | 30 | The first thing we need to do is configure our redux store. Create a new file in the within `/src/` called `redux`. Within this folder, create a new file called `configureStore.js`. 31 | 32 | Open our new file, and add the following imports: 33 | 34 | ```javascript 35 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 36 | import createLogger from 'redux-logger'; 37 | ``` 38 | 39 | `createStore` is the function provided by redux to initialize our store, while `applyMiddleware` is an entry point for us to add any middleware we'd like to apply. 40 | 41 | `combineReducers` is used as your app grows to combine multiple reducers into a single entity. 42 | 43 | `createLogger` is a middleware that enhances our console to give us a detailed look of how data is being handled over time with each action. It's super cool. 44 | 45 | 46 | Under these imports, add the following code: 47 | 48 | ```javascript 49 | const loggerMiddleware = createLogger(); // initialize logger 50 | 51 | const createStoreWithMiddleware = applyMiddleware( loggerMiddleware)(createStore); // apply logger to redux 52 | ``` 53 | 54 | We're not done here, but we'll need to create another file before we can progress. 55 | 56 | ## 3. Modules 57 | 58 | Within `src/redux/` create another folder called `modules`. Within here we'll store all our reducers, action creators, and constants needed to make redux work. We're going to be using an orginizational structure called ducks where we store related constants, reducers, and action creators in a single file, rather than having multiple files for each. I find this makes our code much easier to work with, though there are conflicting points of view here. 59 | 60 | Within the `modules` folder, create 'toDoApp.js'. I like to name my ducks files after the parent component they are most closely associated with. Notice that the filename begins with a lowercase. 61 | 62 | Now were going to create our initial state and our reducer function. This is actually very simple as state is just a JavaScript object and the reducer is just a JavaScript switch statement. It should look like this: 63 | 64 | ```javascript 65 | const initialState = {}; //The initial state of this reducer (will be combined with the states of other reducers as your app grows) 66 | 67 | export default function reducer(state = initialState, action){ // a function that has two parameters, state (which is initialized as our initialState obj), and action, which we'll cover soon. 68 | switch (action.type){ 69 | default: 70 | return state; 71 | } 72 | } 73 | ``` 74 | 75 | ## 4. Configure Store 76 | 77 | Now that we have our first reducer, it's time to add it to our `configureStore.js` file. Go back to that file and add this import statement `import toDoApp from './modules/toDoApp';` 78 | 79 | Now we're going to use the combineReducers function to combine our current reducer with any future reducers that may come along as our app grows. At the bottom of the file, add: 80 | 81 | ```javascript 82 | const reducer = combineReducers({ 83 | toDoApp 84 | }); 85 | ``` 86 | 87 | Finally, add the following line of code to bring it all together: 88 | 89 | ```javascript 90 | const configureStore = (initialState) => createStoreWithMiddleware(reducer, initialState); 91 | export default configureStore; 92 | ``` 93 | 94 | Cool. We're done here. 95 | 96 | ## 5. Connect 97 | 98 | Now that we have a reducer, we need a way to give our app access to it. This will take two more steps, and then we can get to the good stuff. 99 | 100 | We've talked before about class components and functional components. These are sometimes also known as smart components and dumb components. We'll we're going to add one more kind of component into the mix called a container. This is simply a wrapper for a React component that give the component it wraps access to actions (the functions that make a Redux app work), and state. 101 | 102 | To create our first container, first we'll need to create another new folder within `/src/` called `containers`. Within `containers`, create a new file called `toDoAppContainer.js`. 103 | 104 | At the top of this new file, we're going to import the `connect` function from `react-redux` which ties together the container to the component, as well as the component. It should look like: 105 | 106 | ```javascript 107 | import { connect } from 'react-redux'; 108 | import ToDoApp from '../components/ToDoApp.js' 109 | ``` 110 | 111 | `connect` is a function that is called twice, first with two callbacks: `mapStateToProps` and `mapDispatchToProps`. The second time it's called, it we pass in the component we're mapping state and dispatch to. 112 | 113 | We already know what state is, but dispatch is new. When we want something to happen in Redux, we call a dispatch function and give it an action which in turn sends our reducer a constant that tells it how to manipulate state. This will become more clear as we press forward. Currently we don't have any actions to dispatch, so we'll only be using the first for a short while, but we'll write both functions anyway. 114 | 115 | ```javascript 116 | function mapStateToProps(state) { 117 | return { 118 | toDoApp: state.toDoApp // gives our component access to state through props.toDoApp 119 | } 120 | } 121 | 122 | function mapDispatchToProps(dispatch) { 123 | return {}; // here we'll soon be mapping actions to props 124 | } 125 | ``` 126 | 127 | Now all we need to do is export and call the connect function: 128 | 129 | ```javascript 130 | export default connect( 131 | mapStateToProps, 132 | mapDispatchToProps 133 | )(ToDoApp); 134 | ``` 135 | 136 | 6. Provider 137 | 138 | We're nearly done with setting up Redux. For the final step in getting all of this revved, we need to go back to the `app.js` file. 139 | 140 | First, we need to import a few things. We no longer need our `ToDoApp` component, because that is now going to be replaced by our `ToDoAppContainer`. We also need to import our `configureStore` function, as well as a final helper Component from `react-redux` called `Provider`. So add these to the top of the `app.js` file: 141 | 142 | ```javascript 143 | import { Provider } from 'react-redux'; 144 | import ToDoAppContainer from './containers/ToDoAppContainer'; 145 | import configureStore from './redux/configureStore'; 146 | ``` 147 | 148 | `configureStore` is the function we created that takes our combined reducers and our redux middleware and mashes them all together. Let's intialize that with the following line: 149 | 150 | ```javascript 151 | const store = configureStore(); 152 | ``` 153 | 154 | Now, within the JSX of the `App` component, we have two more changes. Change the `ToDoApp` component to the new `ToDoAppContainer` component. Then, we'll wrap it with the `Provider` component provided to us by `react-redux`. This component passes down the state of our entire app to the containers nested within it, giving those containers access to the state. 155 | 156 | Our JSX should now look like this: 157 | 158 | ```javascript 159 | // we pass the store through to Provider with props 160 | 161 | 162 | ``` 163 | 164 | Now go back to the browser. Everything should be functioning as it was before. If not, then something went wronge. Take a look at the source files within this branch to make sure you have everything correct, then move on to the next branch to start moving our logic over to the Redux layer. 165 | 166 | -------------------------------------------------------------------------------- /notes/6-reduxActionsAndReducers.md: -------------------------------------------------------------------------------- 1 | # Redux Actions and Reducers 2 | 3 | Setting up Redux takes some leg work, but once you get passed that, it's a very nice paradim to work with, especially when compaiered to flux, the data handling implementation that it came to replace. 4 | 5 | Before we get started, let's quickly cover a few concepts. Types, Actions, Action Creators, and Reducers are the elements you'll be working with when building a Redux powered app. 6 | 7 | ## Ducks 8 | 9 | First let's talk about how we are originizing our Redux code. We're going to use Ducks, which is a simple system that has a few requirement which are: 10 | 11 | ### Each Ducks file: 12 | - MUST export default a function called reducer() 13 | - MUST export its action creators as functions 14 | - MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE 15 | - MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library 16 | 17 | We'll see these principles in action as we go. 18 | 19 | ## Types 20 | 21 | Types are constants that are typically defined either in their own file or, when following the ducks methodology like we are, at the top of the file containing your reducer. They are `const`s, all uppercase, and my be exported if other reducers need to listen for them. 22 | 23 | Types are listened for within the reducer switch whenever an action is fired off. The action is sent to the reducer with a type, and when that type matches the type within the a case of the switch, some sort of data manipulation is fired off. 24 | 25 | Types look like this: 26 | 27 | ```javascript 28 | const ADD_ITEM = 'ADD_ITEM'; 29 | const DELETE_ITEM = 'DELETE_ITEM'; 30 | ``` 31 | 32 | ## Actions 33 | 34 | Actions are simple javascript objects that at least contain a type, and may also contain data that can be sent to the reducer. When the user clicks on something that has an effect on the state of an app, an action creator sends an action to the reducer where the data manipulation happens. 35 | 36 | An action typically looks like this: 37 | 38 | ```javascript 39 | { type: ADD_ITEM, item: 'Adding this item' } 40 | { type: DELETE_ITEM, index: 1 } 41 | { type: POP_ITEM } 42 | ``` 43 | 44 | ## Action Creators 45 | 46 | Action creators are the functions that create actions and send them to the reducer. They usually return an action, sometimes can dispatch multiple actions (with the help of middleware like thunk), or can begin asyncronous events like API calls (with the help of the Promise middleware). In this tutorial, we'll keep them simple. 47 | 48 | A simple action creature looks like this: 49 | 50 | ```javascript 51 | function addItem(item){ 52 | return { 53 | type: ADD_ITEM, 54 | item // this is new ES6 shorthand for when the key is the same as a variable or perameter within the scope of the object. It's the same as item: item 55 | } 56 | } 57 | ``` 58 | 59 | ## Reducers 60 | 61 | The reducer is typically the only thing that touches the store. It only deals within a particular part of the store, inicialized as `initialState`. It's a pure switch statement that does not directly change the state because state is immutable. That means you cannot use a method like `.pop` or `.push` that manipulates the array it's called on. Luckily for us, we've been keeping our data immutiable this whole time :) Finally, all reducers have a default case that just returns state. 62 | 63 | Here's an example of a reducer: 64 | 65 | ```javascript 66 | const initialState = { 67 | list: [] 68 | }; 69 | 70 | export default function reducer(state = initialState, action){ 71 | switch (action.type){ 72 | case ADD_ITEM: 73 | return Object.assign( 74 | {}, 75 | state, 76 | { list: [...state.list, action.item]} // here we see object.assign again, and we're returning a new state built from the old state without directly manipulating it 77 | ) 78 | default: 79 | return state; 80 | } 81 | } 82 | ``` 83 | 84 | Ok, those are the concepts. Now time for the real work. 85 | 86 | ## 1. Initial state 87 | 88 | The first thing we'll want to do is make sure our data is coming in fine. To do this, add something into the `initialState` within `src/redux/modules/toDoApp`. Here's what mine looks like: 89 | 90 | ```javascript 91 | const initialState = { 92 | list: [{item: 'test', done: false}] // just added this to test that state is being passed down propperly, 93 | newToDo: '' 94 | }; 95 | 96 | export default function reducer(state = initialState, action){ 97 | switch (action.type){ 98 | default: 99 | return state; 100 | } 101 | } 102 | ``` 103 | Now, in `ToDoApp.js`, in the `render()` method, before the return, I added `console.log(this.props)` to see what I get. I got this in my browser console: 104 | 105 | ```javascript 106 | toDoApp: Object 107 | list: Array[1] 108 | 0: "test" 109 | length: 1 110 | __proto__: Array[0] 111 | __proto__: Object 112 | __proto__: Object 113 | ``` 114 | Looks good to me. 115 | 116 | So the test passed. Let's take that data and pass it to the view. For this, all we need to do is to go to the JSX within `ToDoApp` and replace the `listItems` prop in the `List` component, and the `value` prop in the `Input` component. Try to implement this change yourself before moving forward. 117 | 118 | Did you get it? The `List` and `Input` components should look like this: 119 | 120 | ```javascript 121 | 126 | 131 | ``` 132 | 133 | Now, if you look at your app, and see the test list item. If you type in the input box or click something, you should be seeing errors in your console. Nothing happens on submit or click though, so we're going to add some actions. 134 | 135 | ## 3. Input action 136 | 137 | The process we're going to take now is migrate all the logic we have in our `ToDoApp` component into our `toDoApp` module. 138 | 139 | In 'toDoApp.js' (lowercase t), we're going to create new type called `INPUT_CHANGED'. Then create and export a new function called 'onInputChange' that takes `value` as a parameter and returns an action with the type equal to `INPUT_CHANGED` and `value` equal to `value`. These new features should look like this: 140 | 141 | ```javascript 142 | const INPUT_CHANGED = 'INPUT_CHANGED'; 143 | 144 | export function inputChange(newToDo){ 145 | return { 146 | type: INPUT_CHANGED, 147 | newToDo 148 | } 149 | } 150 | ``` 151 | 152 | Now we need to add the logic to add this input to the store within the reducer. Within the switch statement, add: 153 | 154 | ```javascript 155 | case INPUT_CHANGED: 156 | return Object.assign( 157 | {}, 158 | state, 159 | {input: action.value} 160 | ) 161 | ``` 162 | 163 | Now, when the action creator is fired off, the action with the type of `INPUT_CHANGED` will be sent to the reducer, and state will be updated without being modified. Now we need to pass this functionality to the app. 164 | 165 | In `toDoAppContainer.js` within the `mapDispatchToProps` function, we're going to add this new function. First import `onInputChange`, then in the return statement of `mapDispatchToProps` add: `onInputChange: (value) => dispatch(onInputChange(value))`. 166 | 167 | The entire file should look like this: 168 | 169 | ```javascript 170 | import { connect } from 'react-redux'; 171 | import ToDoApp from '../components/ToDoApp.js' 172 | import { 173 | inputChange 174 | } from '../redux/modules/toDoApp'; // we added this 175 | 176 | function mapStateToProps(state) { 177 | return { 178 | toDoApp: state.toDoApp // gives our component access to state through props.toDoApp 179 | } 180 | } 181 | 182 | function mapDispatchToProps(dispatch) { 183 | return { 184 | inputChange: (value) => dispatch(inputChange(value)) // we added this 185 | }; 186 | } 187 | 188 | export default connect( 189 | mapStateToProps, 190 | mapDispatchToProps 191 | )(ToDoApp); 192 | ``` 193 | Now we should have acces to that in our `ToDoApp` component. An easy way to tell is to check in our browser console to see if it shows up in props. If so, you did it correctly. If not, check the source files of this project. 194 | 195 | So, within the `onInputChange` method of `ToDoApp`, replace the line `this.setState({ newToDo: event.target.value});` to `this.props.inputChange(event.target.value);`. To test that this is coming through, go to the browser, find the input box and start typing. You should see some enchanced logs appearing. They'll say `prev state`, `action` and `next state`. This is the function of the Logger middleware, and it's very useful in development. 196 | 197 | ## 4. The other actions 198 | 199 | All of the changes from here on will follow the same pattern as above. If you understand the pattern, try to retrofit the app on your own. If you get stumped, peek at the source code in this repo. Otherwise, let's get started with the next action. 200 | 201 | Next we have `onInputSubmit`. Go to the redux file, `toDoApp.js` and start my making a type constant and an action creator. They should look like this: 202 | 203 | ```javascript 204 | 205 | const INPUT_SUBMIT = 'INPUT_SUBMIT'; 206 | 207 | export function inputSubmit(){ 208 | return { 209 | type: INPUT_SUBMIT 210 | }; 211 | } 212 | ``` 213 | 214 | Now we need to add a case for the type in our reducer that updates state. It should look like this: 215 | 216 | ```javascript 217 | export default function reducer(state = initialState, action){ 218 | switch (action.type){ 219 | case INPUT_SUBMIT: 220 | return Object.assign( 221 | {}, 222 | state, 223 | { list: [...state.list, {item: state.newToDo, done: false }], 224 | newToDo: ''} 225 | ); 226 | case INPUT_CHANGED: 227 | return Object.assign( 228 | {}, 229 | state, 230 | {newToDo: action.value} 231 | ); 232 | default: 233 | return state; 234 | } 235 | } 236 | ``` 237 | 238 | Now we need to add this action to our `toDoAppContainer`. Import it, then add it to the `mapDispatchToProps` function. 239 | 240 | ```javascript 241 | import { connect } from 'react-redux'; 242 | import ToDoApp from '../components/ToDoApp.js' 243 | import { 244 | inputChange, 245 | inputSubmit 246 | } from '../redux/modules/toDoApp'; 247 | 248 | ... 249 | 250 | function mapDispatchToProps(dispatch) { 251 | return { 252 | onInputChange: (value) => dispatch(onInputChange(value)), 253 | inputSubmit: () => dispatch(inputSubmit()) 254 | }; 255 | } 256 | ``` 257 | 258 | Cool. Now we have access to the in the `ToDoApp` component through props. Go there and remove the `setState` portion of the `onInputSubmit` method and call our new function. It should look like this: 259 | 260 | ```javascript 261 | onInputSubmit = (event) => { 262 | event.preventDefault(); 263 | this.props.inputSubmit(); 264 | }; 265 | ``` 266 | 267 | Now repeat this process for the remaining actions. Let's do the remainder in bulk. We just have `onListItemClick` and `deleteListItem` remaining. In the redux file `toDoApp.js` add the remaining constants and action creators. They should look like this: 268 | 269 | ```javascript 270 | const LIST_ITEM_CLICK = 'LIST_ITEM_CLICK'; 271 | const DELETE_LIST_ITEM = 'DELETE_LIST_ITEM'; 272 | 273 | export function listItemClick(index){ 274 | return { 275 | type: { 276 | LIST_ITEM_CLICK, 277 | index 278 | } 279 | } 280 | } 281 | 282 | export function deleteListItem(index) { 283 | return { 284 | type: DELETE_LIST_ITEM, 285 | index 286 | } 287 | } 288 | ``` 289 | 290 | Now we need to adjust the reducer to include these: 291 | 292 | ```javascript 293 | case LIST_ITEM_CLICK: 294 | return Object.assign( 295 | {}, 296 | state, 297 | { 298 | list: [ 299 | ...state.list.slice(0, action.index), 300 | Object.assign({}, state.list[action.index], {done: !state.list[action.index].done}), 301 | ...state.list.slice(action.index+1) 302 | ] 303 | } 304 | ); 305 | case DELETE_LIST_ITEM: 306 | return Object.assign( 307 | {}, 308 | state, 309 | { 310 | list: [ 311 | ...state.list.slice(0, action.index), 312 | ...state.list.slice(action.index+1) 313 | ] 314 | } 315 | ); 316 | ``` 317 | Then pass them through to `ToDoApp` using the `ToDoAppContainer`: 318 | 319 | ```javascript 320 | function mapDispatchToProps(dispatch) { 321 | return { 322 | inputChange: (value) => dispatch(inputChange(value)), 323 | inputSubmit: () => dispatch(inputSubmit()), 324 | deleteListItem: (i) => dispatch(deleteListItem(i)), 325 | listItemClick: (i) => dispatch(listItemClick(i)) 326 | }; // here we're mapping actions to props 327 | } 328 | ``` 329 | 330 | Now, adjust the logic in `ToDoApp`: 331 | 332 | ```javascript 333 | onInputChange = (event) => { 334 | this.props.inputChange(event.target.value); 335 | }; 336 | 337 | onListItemClick = (i) => { 338 | this.props.listItemClick(i) 339 | }; 340 | ``` 341 | 342 | And finally, we can remove the `componentWillMount` method, because the state that it's setting is no longer being used. 343 | 344 | ## There you have it 345 | 346 | You now should have a fully functional To Do App built with Redact, Webpack, Babel and Redux. There are still some more optimizations we can make if we wanted, still more functionality we could add. But what we have now is the first step for your next project. Keep these files with you to reference them when you need. Continue learning by starting with the suggested reading materials in the Readme. But most of all, never stop coding! 347 | -------------------------------------------------------------------------------- /notes/react-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goopscoop/ga-react-tutorial/4e0856aa90af79faedff5f98cc583380352ee3f2/notes/react-lifecycle.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ga-react-tutorial", 3 | "version": "1.0.0", 4 | "description": "# Setup (Before Class)", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --hot --progress --colors", 8 | "build": "webpack --progress --colors" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/goopscoop/ga-react-tutorial.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/goopscoop/ga-react-tutorial/issues" 18 | }, 19 | "homepage": "https://github.com/goopscoop/ga-react-tutorial#readme", 20 | "devDependencies": { 21 | "babel": "^6.3.26", 22 | "babel-core": "^6.3.26", 23 | "babel-loader": "^6.2.0", 24 | "babel-plugin-transform-runtime": "^6.3.13", 25 | "babel-preset-es2015": "^6.3.13", 26 | "babel-preset-react": "^6.3.13", 27 | "babel-preset-stage-0": "^6.3.13", 28 | "css-loader": "^0.23.1", 29 | "react-hot-loader": "^1.3.0", 30 | "webpack": "^1.12.9", 31 | "webpack-dev-server": "^1.14.0" 32 | }, 33 | "dependencies": { 34 | "babel-polyfill": "^6.3.14", 35 | "babel-runtime": "^6.3.19", 36 | "react": "^0.14.5", 37 | "react-dom": "^0.14.5", 38 | "react-redux": "^4.4.1", 39 | "react-router": "^2.0.1", 40 | "redux": "^3.3.1", 41 | "redux-logger": "^2.6.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | class App extends React.Component { 5 | render(){ 6 | return( 7 |
    Hello General Assembly. Welcome to React
    8 | ); 9 | } 10 | } 11 | 12 | ReactDOM.render(, document.getElementById('app')); -------------------------------------------------------------------------------- /talking-points.md: -------------------------------------------------------------------------------- 1 | - What is react: 2 | - The V in MVC 3 | - Built on Components 4 | - The Virtual DOM 5 | - JSX 6 | - Getting Started 7 | + Setup Webpack 8 | * What is Webpack (Browserify) 9 | * Quick look at file structure 10 | * Quick look at webpack.config.js 11 | - Components 12 | + React and ES6 13 | + Structure 14 | + Props (ViewPort) 15 | + Default Props 16 | - Redux 17 | + What is flux/redux 18 | + `npm install --save redux`, 19 | + `npm install --save react-redux` 20 | + `npm install --save redux-logger` 21 | 22 | app.js 23 | `import { Provider, connect } from 'react-redux';` 24 | `import configureStore from './redux/configureStore';` 25 | 26 | configureStore.js 27 | ```javascript 28 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 29 | import createLogger from 'redux-logger'; 30 | //import duck files 31 | 32 | const reducer = combineReducers(//reducers go here) 33 | const loggerMiddleware = createLogger(); 34 | 35 | const createStoreWithMiddleware = applyMiddleware(loggerMiddleware)(createStore); 36 | 37 | const configureStore = (initialState) => createStoreWithMiddleware(reducer, initialState); 38 | export default configureStore; 39 | ``` 40 | 41 | toDoReducers.js 42 | ``` 43 | const ADD_TODO = 'ADD_TODO'; 44 | 45 | export function addTodo(){ 46 | return { 47 | type: ADD_TODO, 48 | other stuff 49 | } 50 | } 51 | 52 | const initialState = { 53 | toDos: [] 54 | } 55 | 56 | export default function reducer(state = initialState, action){ 57 | switch(action.type){ 58 | case ADD_TODO: 59 | return Object.assign({}, 60 | state, 61 | {toDos: state.toDos.concat(action.todo)}); 62 | default: 63 | return state 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | module.exports = { 3 | entry: [ 4 | 'babel-polyfill', 5 | 'webpack/hot/only-dev-server', 6 | './src/app.js' 7 | ], 8 | output: { 9 | path: __dirname + '/build', 10 | filename: "bundle.js" 11 | }, 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | loader: 'babel-loader', 18 | query: { 19 | plugins: ['transform-runtime'], 20 | presets:['es2015', 'react', 'stage-0'] 21 | } 22 | }, 23 | { test: /\.css$/, loader: "style!css" } 24 | ] 25 | }, 26 | plugins: [ 27 | new webpack.NoErrorsPlugin() 28 | ] 29 | }; --------------------------------------------------------------------------------