commit 1fce93a1657196062815e75f149f2a91cbc5db70 Author: Matt Huntington Date: Sun Feb 4 16:23:37 2018 -0500 copy diff --git a/AJAXRedux.md b/AJAXRedux.md new file mode 100755 index 0000000..5cc6dbb --- /dev/null +++ b/AJAXRedux.md @@ -0,0 +1,134 @@ +![In Rhythm](logo.gif) + +# Using AJAX With Redux + +## Lesson Objectives + +1. Make an AJAX GET Request Using Fetch +1. Make an AJAX POST Request Using Fetch +1. Create a SET Reducer for the Store +1. Dispatch a SET action appropriately +1. Dispatch a ADD action appropriately + +## Make an AJAX GET Request Using Fetch + +In `js/index.js` make a test GET request: + +```javascript +fetch('https://stupidcomments.herokuapp.com/comments').then(function(response){ + response.json().then(function(data){ + console.log(data); + }); +}); +``` + +## Make an AJAX POST Request Using Fetch + +In `js/index.js` make a test POST request: + +```javascript +fetch( + 'https://stupidcomments.herokuapp.com/comments', + { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + method: "POST", + body: JSON.stringify({ body:'again from react' }) + } +).then(function(response){ + response.json().then(function(data){ + console.log(data); + }); +}); +``` + +**NOTE** When you're finished with this code, comment it out, so you don't keep creating new comments + +## Create a SET Reducer for the Store + +Our plan is the make a GET request to our API and then set the store to the response's values. + +Create a SET Reducer in `js/store.js`: + +```javascript +let comments = function(state = [], action){ + switch(action.type){ + case 'ADD': + return [...state, action.comment]; + case 'SET': + return action.comments + default: + return state + } +} +``` + +## Dispatch a SET action appropriately + +Import `js/store.js` into `js/components/commentslist.jsx`: + +```javascript +import store from '../store.js'; +``` + +Once our `js/components/commentslist.jsx` component mounts, make the approriate AJAX request and dispatch a SET action. + +```javascript +componentDidMount(){ + fetch('https://stupidcomments.herokuapp.com/comments').then(function(response){ + response.json().then(function(data){ + console.log(data); + }); + }); +} +``` + +Now dispatch a `SET` action: + +```javascript +componentDidMount(){ + fetch('https://stupidcomments.herokuapp.com/comments').then(function(response){ + response.json().then(function(data){ + store.dispatch({type:'SET', comments:data}); + }); + }); +} +``` + +## Dispatch a ADD action appropriately + +In `js/components/commentsform.jsx` alter the `handleSubmit` property to make an AJAX POST request: + +```javascript +const mapDispatchToProps = function(dispatch){ + return { + handleSubmit: function(comment){ + fetch( + 'https://stupidcomments.herokuapp.com/comments', + { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + method: "POST", + body: JSON.stringify(comment) + } + ).then(function(response){ + response.json().then(function(data){ + console.log(data); + }); + }); + } + } +} +``` + +We can see that `data` has a `body` property that we want to use in our dispatch in `js/components/commentsform.jsx`: + +```javascript +response.json().then(function(data){ + dispatch({type:'ADD', comment: data }); +}); +``` diff --git a/Comments.md b/Comments.md new file mode 100755 index 0000000..dc01b4b --- /dev/null +++ b/Comments.md @@ -0,0 +1,303 @@ +![In Rhythm](logo.gif) + +# Comments Component Build + +This builds off of what was built by the end of [the Webpack Lesson](Webpack.md). + +## Lesson Objectives + +1. Create Comments component +1. Create CommentsList component +1. Create CommentsForm component +1. Display an array of comments +1. Make form add new comments +1. Create the ability to add new comments to the Comments component +1. Have the Comments form call the Comments component's addComment function + +## Create Comments component + +Create a directory for components: + +``` +mkdir js/components +``` + +Rename `mylib.js` to `comments.jsx` and move it in the `components` dir: + +``` +mv js/mylib.js js/components/comments.jsx +``` + +Create the Comments class in `js/components/comments.jsx`: + +```javascript +import React from 'react'; + +class Comments extends React.Component { + render(){ + return

Comments Component

+ } +} + +export default Comments; +``` + +Import the Comments component into `js/index.js`: + +```javascript +import ReactDOM from 'react-dom'; +import React from 'react'; +import Comments from './components/comments.jsx'; + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +## Create CommentsList component + +Create a separate file for the comments list: + +``` +touch js/components/commentslist.jsx +``` + +In `js/components/commentslist.jsx` create a basic component: + +```javascript +import React from 'react'; + +class CommentsList extends React.Component { + render(){ + return + } +} + +export default CommentsList; +``` + +Import CommentsList into `js/components/comments.js`: + +```javascript +import React from 'react'; +import CommentsList from './commentslist.jsx' + +class Comments extends React.Component { + render(){ + return
+ +
+ } +} + +export default Comments; +``` + +## Create CommentsForm component + +Create `js/components/commentsform.jsx`: + +``` +touch js/components/commentsform.jsx +``` + +Write a basic component in `js/components/commentsform.jsx`: + +```javascript +import React from 'react'; + +class CommentsForm extends React.Component { + render() { + return
+
+
+ +
+ } +} + +export default CommentsForm; +``` + +Import CommentsForm into `js/components/comments.js`: + +```javascript +import React from 'react'; +import CommentsList from './commentslist.jsx' +import CommentsForm from './commentsform.jsx' + +class Comments extends React.Component { + render(){ + return
+ + +
+ } +} + +export default Comments; +``` + +## Display an array of comments + +In `js/components/comments.jsx`, create a constructor in the Comments class that initializes an array of comments: + +```javascript +constructor(props){ + super(props); + this.state = { + comments: [ + { + body:'this rox', + author:'bob' + }, + { + body:'no, you rox', + author:'sally' + }, + { + body:'we all rox', + author:'zagthor' + } + ] + } +} +``` + +Now pass it off to the CommentsList component: + +```javascript + +``` + +In `js/components/commentslist.jsx` loop through the `comments` prop and display the comments: + +```javascript +import React from 'react'; + +class CommentsList extends React.Component { + render(){ + return + + } +} + +export default CommentsList; +``` + +## Create the ability to add new comments to the Comments component + +In `js/components/comments.jsx` set the comments state property to an empty array: + +```javascript +constructor(props){ + super(props); + this.state = { + comments: [] + } +} +``` + +Add the ability to add a new comment in `js/components/comments.jsx` + +- you have to use `this.setState` or react won't know the state has changed and won't refresh the DOM + +```javascript +addComment(comment){ + this.state.comments.push(comment); //this alone won't notify React to update the DOM + this.setState({ + comments: this.state.comments + }); +} +``` + +Now bind the `addComment` function in the constructor for `js/components/comments.jsx`: + +```javascript +constructor(props){ + super(props); + this.state = { + comments: [] + } + this.addComment = this.addComment.bind(this); +} +``` + +## Have the CommentsForm call the Comments component's addComment function + +In `js/components/comments.jsx` create a property on the CommentsForm element called `handleSubmit` and pass it `this.addComment`: + +```javascript +render(){ + return
+ + +
+} +``` + +In `js/components/commentsform.jsx`, create a handleSubmit function in the `CommentsForm` class definition: + +```javascript +handleSubmit(event){ + event.preventDefault(); + console.log('posting comment'); +} +``` + +Don't forget to create a constructor that binds handleSubmit in `js/components/commentsform.jsx`: + +```javascript +constructor(props){ + super(props); + this.handleSubmit = this.handleSubmit.bind(this); +} +``` + +Now call it on form submit in `js/components/commentsform.jsx`: + +```javascript +return
+
+
+ +
+``` + +Create references to the comment textarea and author input element in `js/components/commentsform.jsx`: + +```html +
+
+``` + +In the `handleSubmit` function of `js/components/commentsform.jsx`, log this element's value: + +```javascript +handleSubmit(event){ + event.preventDefault(); + console.log(this.refs.author.value); + console.log(this.refs.body.value); +} +``` + +Now call the `handleSubmit` function that was passed into the CommentsForm component by the Comments component: + +```javascript +handleSubmit(event){ + event.preventDefault(); + this.props.handleSubmit({ + body: this.refs.body.value, + author: this.refs.author.value + }); +} +``` diff --git a/CommentsRedux.md b/CommentsRedux.md new file mode 100755 index 0000000..b3919db --- /dev/null +++ b/CommentsRedux.md @@ -0,0 +1,160 @@ +![In Rhythm](logo.gif) + +# Integrate Comments Component With Redux + +## Lesson Objectives + +1. Install Redux +1. Create the Store +1. Make CommentsList Subscribe to the Store +1. Make CommentsForm Dispatch Actions +1. When an Action Occurs, make CommentsList Update State +1. Remove Unnecessary Code + +## Install Redux + +``` +npm install redux --save-dev +``` + +## Create the Store + +Create `js/store.js`: + +``` +touch js/store.js +``` + +Write a basic store that handles an `ADD` action and a `default`: + +```javascript +import { createStore } from 'redux' + +let comments = function(state = [], action){ + switch(action.type){ + case 'ADD': + return [...state, action.comment]; + default: + return state + } +} + +let store = createStore(comments); + +export default store; +``` + +## Make CommentsList Subscribe to the Store + +In `js/components/commentslist.jsx`, import the store: + +```javascript +import store from '../store.js'; +``` + +Once the component mounts, subscribe to the store: + +```javascript +componentDidMount(){ + store.subscribe(function(){ + console.log(store.getState()) + }); +} +``` + +## Make CommentsForm Dispatch Actions + +In `js/components/commentsform.jsx`, import the store: + +```javascript +import store from '../store.js'; +``` + +In the handleSubmit function, dispatch an `ADD` action: + +```javascript +handleSubmit(event){ + event.preventDefault(); + this.props.handleSubmit({ + body: this.refs.body.value, + author: this.refs.author.value + }); + store.dispatch({ + type:'ADD', + comment: { + body: this.refs.body.value, + author: this.refs.author.value + } + }); +} +``` + +## When an Action Occurs, make CommentsList Update State + +Set up a state property in `js/components/commentslist.jsx` for comments: + +```javascript +constructor(props){ + super(props) + this.state = { + comments: [] + } +} +``` + +when an action occurs, set the state property (we'll need to use an arrow function for proper bind of `this`): + +```javascript +componentDidMount(){ + store.subscribe(() => { + this.setState({ + comments: store.getState() + }); + }); +} +``` + +Now use that state variable instead of the `comments` prop: + +```javascript +render(){ + return +} +``` + +## Remove Unnecessary Code + +- We no long need to store anything in `js/components/comments.jsx` +- We also don't need to pass anything to child components: + +```javascript +class Comments extends React.Component { + render(){ + return
+ + +
+ } +} +``` + +`js/components/commentsform.jsx` no long needs to call `this.props.handleSubmit`: + +```javascript +handleSubmit(event){ + event.preventDefault(); + store.dispatch({ + type:'ADD', + comment: { + body: this.refs.body.value, + author: this.refs.author.value + } + }); +} +``` diff --git a/CommentsRedux2.md b/CommentsRedux2.md new file mode 100755 index 0000000..e053379 --- /dev/null +++ b/CommentsRedux2.md @@ -0,0 +1,117 @@ +![In Rhythm](logo.gif) + +# Integrate Redux Into Comments Part 2 + +The last section works, but there are some optimizations we can make + +## Lesson Objectives + +1. Install `react-redux` +1. Map Redux State To The CommentsList Component +1. Map a Dispatch ADD Action to a CommentsForm Component property + +## Install `react-redux` + +``` +npm install react-redux --save-dev +``` + +Import `react-redux`'s `Provider` module in `js/index.js`: + +```javascript +import { Provider } from 'react-redux'; +``` + +In `js/index.js` wrap `` in a `` component to make redux available to the `Comments` component. Also set the `store` property to the `store` we import from `./store.js`: + +```javascript +import store from './store.js'; + +ReactDOM.render( + + + , + document.querySelector('main') +); +``` + +## Map Redux State To The CommentsList Component + +In `js/components/commentslist.jsx`, import `connect` from `react-redux`: + +```javascript +import { connect } from 'react-redux'; +``` + +In `js/components/commentslist.jsx` create a function that will map redux `state` to component properties: + +```javascript +const mapStateToProps = function(state){ + return { + comments: state + } +} +``` + +In `js/components/commentslist.jsx` connect the mapper with the component: + +```javascript +const VisibleCommentsList = connect( + mapStateToProps +)(CommentsList); +``` + +In `js/components/commentslist.jsx`, export `VisibleCommentsList`: + +```javascript +export default VisibleCommentsList; +``` + +You can now remove the `constructor`/`componentDidMount` functions and change `this.state.comments.map` to `this.props.comments.map` show we back to using props instead of component state: + +## Map a Dispatch ADD Action to a CommentsForm Component property + +Import `connect` component from `react-redux` into `js/components/commentsForm.jsx`: + +```javascript +import { connect } from 'react-redux'; +``` + +Now create a function that will map a dispatch to a property in `js/components/commentsForm.jsx`: + +```javascript +const mapDispatchToProps = function(dispatch){ + return { + handleSubmit: function(comment){ + dispatch({type:'ADD', comment: comment }); + } + } +} +``` + +Connect the mapper to the `CommentsForm` component in `js/components/commentsForm.jsx`: + +```javascript +const VisibleCommentsForm = connect( + null, //normally the mapStateToProps function + mapDispatchToProps +)(CommentsForm) +``` + +Export the `VisibleCommentsForm` component: + +```javascript +export default VisibleCommentsForm; +``` + +Refactor `handleSubmit` to this the `handleSubmit` component prop: + +```javascript +handleSubmit(event){ + event.preventDefault(); + this.props.handleSubmit({ + body: this.refs.body.value, + author: this.refs.author.value + }); +} +``` diff --git a/ES6.md b/ES6.md new file mode 100755 index 0000000..6ead97e --- /dev/null +++ b/ES6.md @@ -0,0 +1,723 @@ +![In Rhythm](logo.gif) + +# ES6 - The Future Is Now! (Sort of) + +## Lesson Objectives + +1. What is ES6 +1. strict mode +1. understanding MDN +1. polyfills and shims +1. babel/transpiling +1. IFFE +1. Block level scoping with let +1. const +1. arrow functions +1. Classes +1. for...of +1. default value setting +1. Array.isArray() +1. argument object +1. spread and rest operators +1. Template Literals +1. Object literal extensions +1. Destructuring +1. swap (desconstucturing method) + +1. symbols +1. helper functions (trunc, entries, values, keys) +1. unicode +1. Maps/Sets, WeakMaps/WeakSets +1. proxies +1. promises +1. generators + +## What is ES6 + +ECMAScript is the standard version of JavaScript. ECMA stands for European Computer Manufacturers Association. ES5 became standard in 2009. ES6 (or ES2015), became standard in 2015. But just because it is made standard, it doesn't mean that all browsers/environments are ready/compliant. + +## Strict Mode + +Adding 'use strict' to the top of your code, (see Babel screenshot) will let you opt in to some newer features of JS! [More info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) + +## understanding MDN + +- Lets you know which methods/syntaxes are standard, experimental, depreciated, when things were added and more including shims/polyfills. + - [forEach on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) +- Quickly see if a method is standard[no notation], experimental[flask], depreciated (removed from standards, may be removed)[thumbs down], or obsolete (likely removed altogether in newer environments)[trash can] + + ![Screenshot left bar, objects](https://i.imgur.com/nSqSXwf.png) + +- See more information about when the method was defined implemented: + + ![Screenshot- forEach Specifications section](https://i.imgur.com/7bPC29H.png) + +## Polyfills/Shims + +Some people call them polyfills, some call them shims; either way: It is code that is a fallback that provides the functionality when the expected functionality is not available (because old browser, etc. ). Many entries in MDN have polyfills. This is also a great way to look at how the ECMAScript standard code is written (a lot of examples are directly from the official ECMAScript docs) + +An example Polyfill: + +![Screenshot- forEach polfill section](https://i.imgur.com/e0G8ZCB.png) + +## babel/transpiling + +Now! But it depends where. Node.js supports most/nearly all new things. [Chrome, Firefox, Edge, and Safari have been keeping up well](http://caniuse.com/#search=es6). But new features are being added and tested. To be sure that code will run across many platforms (never forget the people who still use IE8), many people have come to rely on transpilers/compilers. Transpilers/Compilers take 'dialects' of standard code (incuding ES6, coffeescript, typescript and more) and convert the code for you. A popular one for ES6 is [Babel](https://babeljs.io/), they have both repl (Read, Evaluate, Print, Loop) and an npm module/gem. + + ![babel repl](https://i.imgur.com/SKLMkIU.png) + +## IIFE + +Normally, variable declarations, are "hoisted" up to the top of whatever function they are declared in (or global if no function exists). This can lead to some weird moments: + +```JavaScript +var a = 2; + +if(true){ + var a = 4; +} + +console.log(a); //in many programming languages, this would be 2 +``` + +and a famous interview question: + +```JavaScript +for(var i=0; i<5; i++){ + setTimeout(function(){ + console.log(i) //you'd expect 0,1,2,3,4 + },500 * i); +} +``` + +IIFE - Immediately Invoked Function Expression - often used to create a block of scope. This is not an ES6 thing, but rather an example of an old way to get block scoping. + +```JavaScript +var a = 2; + +(function IIFE(){ + var a = 4; + console.log ('Inside the IFFE, the value of a is', a ); //"Inside the IFFE, the value of a is 4" +})(); + +console.log ( 'Outside the IFFE, the value of a is', a ); //"Outside the IFFE, the value of a is 2" +``` + +And now the loop with setTimeout: + +```JavaScript +for(var i=0; i<5; i++){ + (function(i){ + setTimeout(function(){ + console.log(i) + },500 * i); + })(i) +} +``` + +## Block Scoping with let + +A new way to get block level scoping is with the keyword let, and the use of `{}` as in any `{}`, not just tied to a function! Note: `let` is NOT meant to completely replace `var`! + +```JavaScript +var a = 2 + +{ + let a = 4 + console.log( 'the value of b inside the `{}` is', a); //"block level scope" +} + +console.log ('the value of b outside of the `{}` is', a) //"global scope" +``` + +And now the loop with setTimeout: + +```JavaScript +for(let i=0; i<5; i++){ + setTimeout(function(){ + console.log(i) //you'd expect 0,1,2,3,4 + },500 * i); +} + +console.log(i); //notice i is not defined outside of the loop +``` + +## Const + +Another new way to declare a variable. This will block the value from being reassigned. + +```JavaScript +const pi = 3.14; + pi = "pie" //TypeError: Assignment to constant variable. + +const whatsForLunch = [ "Nuts", "Coffee" ] +whatsForLunch.push( "Yogurt" ) //you can still call object methods that alter the variable +console.log( whatsForLunch) +whatsForLunch.shift(); //you can still call object methods that alter the variable +console.log( whatsForLunch ) +whatsForLunch = ["salad"]; //TypeError: Assignment to constant variable. +``` + +## Arrow Functions + +A new way to write a function expression (arrow functions are always anonymous functions), with fun shortcuts: + +### Shorthands + +Basic: + +```JavaScript +var basicES5 = function(){ + return 'oh hai'; +} + +//take out the keyword 'function' and add in '=>' +var basicES6 = () => { + return 'oh hai' +} +``` + +If the body of the function is just one expression that gets returned, we can remove the `{}` and ES6 knows to return whatever comes after the `=>` + +```JavaScript +var basicES6 = () => 'oh hai' +``` + +Multiple arguments: + +```JavaScript +//ES5 - Multiple arguments +var sumES5 = function (a, b){ + return a+b; +} + +console.log('ES5 sum:',sumES5(5,5)); + +//ES6 - Multiple arguments +var sumES6 = ( a, b ) => a + b; + +console.log('ES6 sum:', sumES6(6,6)); +``` + +If we only have one argument to the function, we can leave out the `()` + +```JavaScript +//ES5 - one argument +var squareES5 = function (c){ + return c*c; +} +console.log('ES5 square:', squareES5(5)); + +//ES6 - one argument +var squareES6 = c => c*c +console.log('ES6 square:',squareES6(6)); +``` + +### Binding + +Here is an example of the arrow function binding this + +```JavaScript +// Create constructor function, inputs name, has default friend values +function Person ( name , friends = ["Charlie", "Dennis", "Ron", "Sweet Dee", "Frank"]){ + this.name = name; + this.friends = friends; + + //Add four methods: + + // The first, `secret Admirer`, `this.name` is undefined in the forEach function + this.secretAdmirer = function (){ + this.friends.forEach(function ( f ){ + console.log( this.name + " sends flowers to " + f ); + }); + } + + //The second one is the way we got around the issue of `this` - which was to set the desired `this` to a variable called `that` or `self` or something similar: + + this.giveOldSchoolLove = function (){ + var self = this; + this.friends.forEach(function ( f ){ + console.log( self.name + " likes " + f ); + }); + } + + // we could also use .bind() + this.giveBindingAffection = function (){ + this.friends.forEach(function ( f ){ + console.log( this.name + " makes friendship bracelets for " + f ) + }.bind(this)); + } + + //Finally, by using the arrow function, `this` is already bound: + + this.giveNewSchoolLove = function (){ + this.friends.forEach(f => console.log( this.name + " hearts " + f )); + } +} + +//See examples +k = new Person ( "Matt" ); +console.log( 'Secret Admirer:' ); +k.secretAdmirer(); +console.log( 'Show old school love:' ); +k.giveOldSchoolLove(); +console.log( 'Show new school love:' ); +k.giveNewSchoolLove(); +``` + +## Support for Classes + +```JavaScript +class Cat { + constructor(name) { + this.name = name; + } + makesNoises() { + return (this.name + 'meows'); + } + purrs(){ + return (this.name + 'purrs'); + } +} + +class Lion extends Cat { + makesNoises() { + return (this.name + 'roars!'); + } + eatHuman(){ + return 'yum'; + } +} + +var myCat = new Cat("Kitty"); +console.log(myCat.makesNoises()); +console.log(myCat.purrs()); + +var myLion = new Lion("Simba"); +console.log(myLion.makesNoises()); +console.log(myLion.purrs()); +console.log(myLion.eatHuman()); +``` + +## for...of + +Until now, the best way to loop through an array was either: + +```JavaScript +var array = [1,4,6]; +for(var i =0; i < array.length; i++){ + console.log(array[i]); +} +``` + +Or: + +```JavaScript +var array = [1,4,6]; +array.forEach(function(element){ + console.log(element); +}); +``` + +But now we can do: + +```JavaScript +var array = [1,4,6]; +for(let element of array){ + console.log(element); +} +``` + +## Default values + +When creating a constructor and you wanted a default value, you previously had to write something like this: + +```JavaScript +function Beverage( type ){ + this.type = type || "water"; +} +var breakfast = new Beverage(); +var breakfast2 = new Beverage( 'beer' ); +console.log ( breakfast ); +console.log ( breakfast2 ); +``` + +Now you can do this in ES6 + +```JavaScript +function Beverage ( type = 'sparkling water' ){ + this.type = type; +} +var breakfast = new Beverage(); +var breakfast2 = new Beverage( 'rocket fuel' ); +console.log ( breakfast ); +console.log ( breakfast2 ); +``` + +## Array.isArray() + +If you have tried to confirm if something is an array with ES5, you may have found it frustrating: + +```JavaScript +var arr = [1,2,3]; + +console.log(typeof arr); //'object' +``` + +You could do + +```JavaScript +arr instanceof Array; // true +``` + +But now there is a new method! + +```JavaScript +Array.isArray(arr); //true +``` + +## Argument Object + +This isn't an ES6 thing, but should help expand your knowledge of arguments in JS. + +The arguments object is an array-like object (but not fully an array, so you cannot always call array methods on it). It allows for a lot of flexibility in the number of arguments a function can take: + +```JavaScript +function sum (){ + var sum = 0; + for(var i = 0; i < arguments.length; i++ ){ + sum += arguments[i]; + } + return sum; +} + +console.log( sum(1,2,3,4) ); //10 +``` + +## Spread and Rest Operators + +### Spread operator: + +Spread Operator will take an array, and convert each element into its own value. It "spreads" the elements out into values + +Let's look at `Math.max()` which takes any number of arguments and finds the maximum value + +```JavaScript +//Standard syntax +console.log(Math.max(1,5,-6)); // 5 +``` + +- But what if you have an array of values? +- Converting each to a variable and then adding them as arguments is a pain, until now: + +```JavaScript +var z = [1,5,-2,8,-9,17,-22]; +console.log(Math.max(...z)); // 17 +``` + +### Rest operator: + +The rest operator gathers many values into an array. It's basically the opposite of spread. + +```JavaScript +function returnOnlyNums(...arrayParam){ + var nums = arrayParam.filter(arg => typeof arg === 'number'); + return nums; +} + +console.log( returnOnlyNums(44, false, 'pizza', 45, {season: "winter"}, [1,2,3,4,5,], 2, 9) ); // [ 44, 45, 2, 9 ] +``` + +## Template Literals (String Interpolation) + +Template literals allow you to insert variables into strings with out having to use `+` + +The old way: + +```JavaScript +var name = "Matt" +console.log ('Hello '+ name); +``` + +Now we can do this: + +```JavaScript +var name = "Matt" +console.log (`Hello ${name}.`); +``` + +You can even evaluate JavaScript within the Template Literal: + +```JavaScript +var name = "Matt"; + +function yell(value){ + return value.toUpperCase(); +} + +console.log (`Hello ${yell(`${name}`)}!`); +``` + +Multiple lines used to suck: + +```JavaScript +var adjective = 'awesome' +var template = 'I wrote a haiku\n'+ +'This is so very ' + adjective + '\n'+ +'Now I am finished'; + +console.log(template); +``` + +Now they're easy! + +```JavaScript +var adjective = 'awesome'; +var template = `I wrote a haiku +This is so very ${adjective} +Now I am finished`; + +console.log(template); +``` + +## Object literal upgrades + +Instead of: + +```JavaScript +var a = 8; +var b = 9; +var c = 10; + +var es5obj = { + a:a, + b:b, + c:c, + d : function (){ + console.log(this.a, this.b, this.c); + } + +} +es5obj.d(); +``` + +Now do: + +```JavaScript +var a = 8; +var b = 9; +var c = 10; + +var es6obj = { + a, + b, + c, + d(){ + console.log(this.a, this.b, this.c); + } +} +es6obj.d(); +``` + +## Destructuring + +Destructuring pulls variables out of their "structure" (array or object) + +### Array Destructuring + +You can assign values an array to variables easily: + +```JavaScript +var a, b; +[a,b] = [10,12] +console.log(a); +console.log(b); +``` + +You can also declare in the same line as assignment: + +```JavaScript +var [a,b] = [10,12] +console.log(a); +console.log(b); +``` + +You can ignore certain values: + +```JavaScript +var a, b; +[a,,b] = [1,2,3,4]; +console.log(a); +console.log(b); +``` + +You can also use a rest operator to gather variables into an array after a certain point: + +```JavaScript +var a, b, rest; +[a,b,...rest] = [10, 20, 30, 40, 50]; +console.log(a); +console.log(b); +console.log(rest); +``` + +You can set defaults as well: + +```JavaScript +var a, b; + +[a=5, b=7] = [1]; +console.log(a); // 1 +console.log(b); // 7 +``` + +### Object Destructuring + +Pull variables out of an object + +```JavaScript +var p, q; + +{p, q} = {p: 42, q: true}; + +console.log(p); // 42 +console.log(q); // true +``` + +You can also change the names of the variables: + +```JavaScript +var o = {p: 42, q: true}; +var {p: foo, q: bar} = o; + +console.log(foo); // 42 +console.log(bar); // true +``` + +You can also do default values: + +```JavaScript +var {a = 10, b = 5} = {a: 3}; + +console.log(a); // 3 +console.log(b); // 5 +``` + +Use this for default options arguments: + +```JavaScript +function drawES2015Chart({size = 'big', cords = {x: 0, y: 0}, radius = 25} = {}) { + console.log(size, cords, radius); +} + +drawES2015Chart({ + cords: {x: 18, y: 30}, + radius: 30 +}); +``` + +You can pull values from a function argument that's an object: + +```JavaScript +var user = { + id: 42, + displayName: 'jdoe', + fullName: { + firstName: 'John', + lastName: 'Doe' + } +}; + +function userId({id}) { + return id; +} + +console.log('userId: ' + userId(user)); // "userId: 42" +``` + +If you have a property name which is not a valid variable name, you can reassign it: + +```JavaScript +const foo = { 'fizz-buzz': true } +const { 'fizz-buzz': fizzBuzz } = foo +console.log(fizzBuzz); +``` + +Lastly, you can compute a variable name: + +```JavaScript +let key = 'z'; +let {[key]: foo} = {z: 'bar'}; + +console.log(foo); // "bar" +``` + +## Swap values: File under `Destructuring Assignment` + +If you wanted to swap the value of x and y with es5, you had to do + +```JavaScript +var x = true; +var y = false; +var temp; + +temp = x; +x = y; +y = temp; +console.log(x,y); +``` + +The new way will return x and y to original values in this case + +```JavaScript +var x = true; +var y = false; + +[x,y] = [y,x]; + +console.log(x,y); +``` + +
Note +The reason this is special and new is because we have to +remember that array values are normally passed by reference. +It is also unusual to have an array that is not assigned to a variable + +```JavaScript +// Pass by reference +var a = 1; +var b = 2; + +var originalArray = [a,b]; +console.log('is `a` equal to orginalArray[0]:', a === originalArray[0]);//true + +var newArray = originalArray; + +//will reverse BOTH arrays (because it is actually two references to the same array) +newArray.reverse() + +console.log('This is newArray.reverse():', newArray) +console.log('This is originalArray after newArray has been reversed', originalArray) +console.log('is `a` equal to orginalArray[0]:', a === originalArray[0]);//false +``` + +To make a duplicate array that is not passed by reference, you would have to do something like: + +```JavaScript +var a = 1; +var b = 2; + +var anotherOriginalArray = [a,b]; +console.log('is `a` equal to anotherOrginalArray[0]:', a === anotherOriginalArray[0]);//true + +var trueNewArray = anotherOriginalArray.map(function(e){ + return e; +}); + +console.log('this is trueNewArray', trueNewArray); +trueNewArray.reverse(); +console.log('\nThis is trueNewArray.reverse():', trueNewArray); +console.log('This is anotherOriginalArray after trueNewArray has been reversed', anotherOriginalArray); +console.log('is `a` equal to anotherOriginalArray[0]:', anotherOriginalArray[0] === a); //true +``` +
diff --git a/README.md b/README.md new file mode 100755 index 0000000..357b363 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +![In Rhythm](logo.gif) + +# Training Layout + +1. Day 1: [Intro To ES6](/ES6.md) +1. Day 1: [Essential Tools (Atom/JSX + Babel.io)](/Tools.md) +1. Day 1/2: [React Basics](/ReactBasics.md) +1. Day 2: [Essential Tools (React Dev Tools)](/Tools.md) +1. Day 2: [Webpack](/Webpack.md) +1. Day 3: [Comments Component Build](/Comments.md) +1. Day 3: [Intro To Redux](/Redux.md) +1. Day 3: [Integrate Redux Into Comments](/CommentsRedux.md) +1. Day 3: [Integrate Redux Into Comments Part 2](/CommentsRedux2.md) +1. Day 3: [AJAX And Redux](/AJAXRedux.md) +1. Day 3: [React Router](/ReactRouter.md) diff --git a/ReactBasics.md b/ReactBasics.md new file mode 100755 index 0000000..bc5b22d --- /dev/null +++ b/ReactBasics.md @@ -0,0 +1,1177 @@ +![In Rhythm](logo.gif) + +# Intro to React + +# Lesson Objectives + +1. Define what React Is +1. Describe how react differs from most other front-end frameworks +1. Create a basic component +1. Assign JSX to a variable +1. Embed an expression into JSX +1. Create multi-line JSX variables +1. Create a custom component +1. Customize an instance of a component with properties +1. Handle user events +1. Conditionally render HTML +1. Create multiple elements from an array +1. Pass properties to children +1. Customize a component by using the tag's content +1. Change a component's state through interaction +1. Create references to the tags in a component +1. Update child component properties with state values +1. Call parent component methods +1. Make AJAX requests within React +1. Handle component lifecycle events +1. Style a component + +## Define what React Is + +React is a front-end framework which is designed to make building a Single Page JavaScript Application easier + +## Describe how react differs from most other front-end frameworks + +- Most other front-end frameworks attempt to separate data from presentation in some kind of an MVC structure + - For example + - One set files for HTML (View) + - One set of files for your Controllers + - One set of files for your Models (data) + - Great for teams where developers specialize on one technology (CSS or JavaScript) +- React puts all the Views, Controllers, and Models for one small section of the application together into one file + - Great for teams where one developer handles just a small section of the application and that's it + +## Create a basic component + +- Let's create a component which is just an H1 tag +- We'll insert it into the `main` tag of our html + +```JavaScript +ReactDOM.render( +

Hello, world!

, + document.querySelector('main') +); +``` + +- This code will find the main tag and render an `h1` tag inside of it + - **NOTE** we can't set class with the `class` attribute anymore + - We have to use className so it doesn't get confused + - All other attributes should work normally +- React mixes HTML with JavaScript (JSX) + - Notice that the HTML does not have quotes or backticks around it + +Let's set up the HTML + +```HTML + + + + + + + + + + + +
+ + +``` + +Let's talk about all those script tags + +- react.js + - The core React functionality + - React can be used for applications that aren't in the browser + - so it's separated out from code that deals with the browser +- react-dom.js + - This allows the core React js to work with the browser's DOM +- browser.min.js (babel-core) + - Babel is a transpiler that will translate ES6 code to ES5 + - it also handles turning JSX into regular JS + +## Assign JSX to a variable + +JSX just renders into regular JavaScript, so we can assign it to variable: + +```JavaScript +const myJSX =

Hello, World!

+ +ReactDOM.render( + myJSX, + document.querySelector('main') +); +``` + +## Embed an expression into JSX + +We can create variables and insert them into our JSX: + +```JavaScript +const user = "Matt"; +const myJSX =

Hello, {user}!

+ +ReactDOM.render( + myJSX, + document.querySelector('main') +); +``` + +We can even execute functions inside the `{}` + +```JavaScript +const formatUser = function(user){ + return user + "!!" +} +const user = "Matt"; +const myJSX =

Hello, {formatUser(user)}

+ +ReactDOM.render( + myJSX, + document.querySelector('main') +); +``` + +## Create multi-line JSX variables + +JSX can be split onto multiple lines: + +```JavaScript +const formatLocation = function(location){ + return location + "!!" +} +const location = "Matt"; +const myJSX = +
+

Hello, {formatLocation(location)}

+ Welcome to the app +
+ +ReactDOM.render( + myJSX, + document.querySelector('main') +); +``` + +## Create a custom component + +With JSX, we can create our own tags!! + +```JavaScript +class Heading extends React.Component { + render() { + return

Hello, World!

; + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +- This mimics what is going on with Web Components +- The idea is that we can write our own custom tags, which are much more semantic, and they will render as regular HTML + +Now that our Heading code has been encapsulated as component, it is easy to reuse: + +```JavaScript +class Heading extends React.Component { + render() { + return

Hello, World!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +**NOTE: There must be one single top level element for JSX, you can't have two siblings be at the top of that component** + +## Customize an instance of a component with properties + +We can customize each instance of a component with properties + +```JavaScript +class Heading extends React.Component { + render() { + return

Hello, {this.props.name}!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +## Handle user events + +We can handle user events (clicks, hover, etc) by defining event handlers and registering them right on the element: + +```JavaScript +const sayHello = function() { + alert("oh hai"); +} + +class Heading extends React.Component { + render() { + return

Hello, {this.props.name}!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +It's a little more component-y if we make these functions part of the component, though: + +```JavaScript +class Heading extends React.Component { + sayHello() { + alert("oh hai"); + } + render() { + return

Hello, {this.props.name}!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +What if we want to get at properties of the component? + +```JavaScript +class Heading extends React.Component { + sayHello() { + console.log(this); //null?!? + } + render() { + return

Hello, {this.props.name}!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +Normally, these event handlers don't have `this` bound correctly. We have to manually do this: + +```JavaScript +class Heading extends React.Component { + constructor(props) { + super(props); + this.sayHello = this.sayHello.bind(this); + } + sayHello() { + console.log(this.props); + alert("My name is " + this.props.name); + } + render() { + return

Hello, {this.props.name}!

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +## Conditionally render HTML + +Depending on some condition, you may want to render different HTML: + +```JavaScript +class Heading extends React.Component { + render() { + if(this.props.userType === 'admin'){ + return

Hello! You are an admin

; + } else { + return

Hello!

; + } + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +We've got some redundant code here, though. We can use an inline ternary operator if we'd like: + +```JavaScript +class Heading extends React.Component { + render() { + return

+ Hello! {(this.props.userType==='admin')?'You are an admin':''} +

; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +You can also render JSX, conditionally: + +```JavaScript +class Heading extends React.Component { + render() { + return
+

Hello!

+ { + (this.props.userType==='admin')? + ( + You are an admin + ): + null + } +
; + } +} + +ReactDOM.render( +
+ + +
, + document.querySelector('main') +); +``` + +## Create multiple elements from an array + +If we have an array of values, we can generate JSX elements from it: + +```JavaScript +const nums = [1,5,8,10]; +class List extends React.Component { + render() { + return ; + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +Each element that's being generated, must have a unique key to help React identify which elements have been changed, added, or removed: + +```JavaScript +const nums = [1,5,8,10]; +class List extends React.Component { + render() { + return ; + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +## Pass properties to children + +You can have components within components: + +```JavaScript +const nums = [1,5,8,10]; + +class ListItem extends React.Component { + render(){ + return
  • This is a list item
  • + } +} + +class List extends React.Component { + render() { + return ; + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +To have the number show up, we simply create a property on the ListItem component + +```JavaScript +const nums = [1,5,8,10]; + +class ListItem extends React.Component { + render(){ + return
  • This is a list item: {this.props.number}
  • + } +} + +class List extends React.Component { + render() { + return ; + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +## Customize a component by using the tag's content + +If using the component's tag's attribute doesn't seem semantic, you can use the content of the tag. + + +```JavaScript +class Person extends React.Component { + render() { + return
    + The name of the person is {this.props.children} +
    ; + } +} + +ReactDOM.render( +
    + Bob + Sally +
    , + document.querySelector('main') +); +``` + +You can further customize the content with tags and additional HTML/JSX: + +```JavaScript +class Person extends React.Component { + render() { + return
    + The name of the person is {this.props.children} +
    ; + } +} + +ReactDOM.render( +
    + + Bob. He is awesome. + + Sally. All hail Sally +
    , + document.querySelector('main') +); +``` + +## Change a component's state through interaction + +We want to interact with a component, and have it visually change. To do this, we use the component's "state" + +1. Create a basic form: + + ```JavaScript + class Auth extends React.Component { + render(){ + return
    + + +
    ; + } + } + + ReactDOM.render( + , + document.querySelector('main') + ); + ``` + +1. In the component's constructor function, initialize the state object: + + ```JavaScript + constructor(props){ + super(props) + this.state = { username: "Not logged In" } + } + ``` + +1. Display the component's state's username property: + + ```JavaScript + render(){ + return
    + {this.state.username}
    + + +
    ; + } + ``` + +1. Create an event handler for changing the user name field: + + ```JavaScript + handleChangeName(event){ + console.log('changed user name'); + } + ``` + +1. Set up handleChangeName so that it can access instance properties: + + ```JavaScript + constructor(props){ + super(props) + this.handleChangeName = this.handleChangeName.bind(this); + this.state = { username: "Not logged In" } + } + ``` + +1. When the input field changes, call handleChangeName: + + ```JavaScript + render(){ + return
    + {this.state.username}
    + + +
    ; + } + ``` + +1. Have handleChangeName update the component's state: + + ```JavaScript + handleChangeName(event){ + this.setState({username:event.target.value}); + } + ``` + +## Create references to the tags in a component + +The previous section for updating state can often be overly complex when dealing with multiple form elements, especially when all you need is a reference to the form elements' values when submitting the form + +1. Change input to have a reference, instead of an event handler: + + ```html + + ``` + +1. Replace handleChangeName with a form submission handler that references the text input + + ```JavaScript + handleFormSubmit(event){ + event.preventDefault(); + this.setState({username:this.refs.username.value}); + } + ``` + +1. Update constructor with correct event handler + + ```JavaScript + constructor(props){ + super(props) + this.handleFormSubmit = this.handleFormSubmit.bind(this); + this.state = { username: "Not logged In" } + } + ``` + +## Update child component properties with state values + +We can pass state values in as properties of child components + +1. Create a Greeting component + + ```JavaScript + class Greeting extends React.Component { + render(){ + return

    Welcome {this.props.name}

    + } + } + ``` + +1. Use Greeting component in Auth component + + ```JavaScript + render(){ + return
    + + + +
    ; + } + ``` + +## Call parent component methods + +Sometimes you want to pass information to a parent component + +1. Delete Greeting component class +1. Delete Greeting component instance within Auth JSX +1. You will no longer need state within Auth component + + ```JavaScript + constructor(props){ + super(props); + this.handleFormSubmit = this.handleFormSubmit.bind(this); + } + handleFormSubmit(event){ + event.preventDefault(); + } + ``` + +1. Create a Heading around the Auth Section + + ```JavaScript + class Heading extends React.Component { + render(){ + return
    +

    Welcome

    + +
    + } + } + + ReactDOM.render( + , + document.querySelector('main') + ); + ``` + +1. Create constructor for Heading with state properties for username + + ```JavaScript + class Heading extends React.Component { + constructor(props){ + super(props); + this.state = { username: null } + } + render(){ + return
    +

    Welcome {this.state.username}

    + +
    + } + } + ``` + +1. Create a method for Heading that will update the state + + ```JavaScript + updateUsername(newName){ + this.setState({username: newName}); + } + ``` + +1. Bind updateUsername in constructor + + ```JavaScript + constructor(props){ + super(props); + this.state = { username: null } + this.updateUsername = this.updateUsername.bind(this); + } + ``` + +1. Pass this function in as a property to Auth + + ```JavaScript + render(){ + return
    +

    Welcome {this.state.username}

    + +
    + } + ``` + +1. When the Auth component handles the form submission, call that property/function + + ```JavaScript + handleFormSubmit(event){ + event.preventDefault(); + this.props.onLogin(this.refs.username.value); + } + ``` + +## Make AJAX requests within React + +React doesn't have any built-in functionality to handle AJAX. Either use jQuery, fetch, or some other library to handle this + +1. Set up a form that logs value of input on submit: + + ```JavaScript + class OMDBQueryForm extends React.Component { + constructor(props){ + super(props); + this.queryOMDB = this.queryOMDB.bind(this); + } + queryOMDB(event){ + event.preventDefault(); + console.log(this.refs.title.value); + } + render(){ + return
    + + +
    + } + } + + ReactDOM.render( + , + document.querySelector('main') + ); + ``` + +1. When submitting, make request to OMBD + + ```JavaScript + queryOMDB(event){ + event.preventDefault(); + fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then(function(response){ + response.json().then(function(data){ + console.log(data); + }); + }); + } + ``` + +1. Create a component to handle movie data + + ```JavaScript + class MovieInfo extends React.Component { + render(){ + return + } + } + ``` + +1. Add it to OMDBQueryForm + + ```JavaScript + render(){ + return
    + + + +
    + } + ``` + +1. Set up state for found movie + + ```JavaScript + constructor(props){ + super(props); + this.queryOMDB = this.queryOMDB.bind(this); + this.state = { foundMovie: null } + } + ``` + +1. `this` inside the fetch callback is not the instantiated component. Let's use arrow functions to fix this + + ```JavaScript + queryOMDB(event){ + event.preventDefault(); + fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then((response) => { + response.json().then((data) => { + console.log(this); + }); + }); + } + ``` + +1. Now we can set the state of the form appropriately + + ```JavaScript + queryOMDB(event){ + event.preventDefault(); + fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then((response) => { + response.json().then((data) => { + this.setState({foundMovie: data}); + }); + }); + } + ``` + +1. And pass on the variable to the MovieInfo component + + ```html + + ``` + +1. Within the MovieInfo component, we can display the info appropriately + + ```JavaScript + render(){ + return + } + ``` + +1. We'll get an error on page load because this.props.data isn't defined initially. Let's display the component conditionally + + ```JavaScript + render(){ + return
    + + + { + (this.state.foundMovie !== null)? + + : + null + } +
    + } + ``` + +## Handle component lifecycle events + +A component has specific moments in its life: + +1. It is created +1. It is updated +1. It is destroyed + +We can perform actions during each of these moments. Let's create an app that just counts down starting at 100. + +1. Create the setup code: + + ```JavaScript + class Counter extends React.Component { + render(){ + return
    + The value: +
    + } + } + + ReactDOM.render( + , + document.querySelector('main') + ); + ``` + +1. Create a state property for the count + + ```JavaScript + class Counter extends React.Component { + constructor(props){ + super(props) + this.state = { count: 100 } + } + render(){ + return
    + The value: {this.state.count} +
    + } + } + ``` + +1. Once the component has loaded, we want to call a function every 1000 milliseconds: + + ```JavaScript + componentDidMount(){ + window.setInterval( + function(){ + console.log('tick'); + }, + 1000 + ) + } + ``` + +1. The problem is that `this` is not correctly bound + + ```JavaScript + componentDidMount(){ + window.setInterval( + function(){ + console.log(this); + }, + 1000 + ) + } + ``` + +1. Let's use an arrow function + + ```JavaScript + componentDidMount(){ + window.setInterval( + () => { + console.log(this); + }, + 1000 + ) + } + ``` + +1. Now we can decrement the state property + + ```JavaScript + componentDidMount(){ + window.setInterval( + () => { + this.setState({ count: (this.state.count - 1) }); + }, + 1000 + ) + } + ``` + +1. Let's create a container that will bring the counter into existence: + + ```JavaScript + class Container extends React.Component { + render(){ + return + } + } + + ReactDOM.render( + , + document.querySelector('main') + ); + ``` + +1. Now were going to create a button that will create the counter and create a state property which will determine whether or not to show the counter + + ```JavaScript + class Container extends React.Component { + constructor(props){ + super(props) + this.state = { buttonExists: false } + } + render(){ + return
    + + { + (this.state.buttonExists)? + + : + null + } +
    + } + } + ``` + +1. When the button is pressed, it toggles the counter's existence + + ```JavaScript + class Container extends React.Component { + constructor(props){ + super(props) + this.state = { buttonExists: false } + this.toggleButton = this.toggleButton.bind(this); //bind function + } + toggleButton(){ //create function + this.setState({buttonExists: !this.state.buttonExists}); + } + render(){ //add onClick listener + return
    + + { + (this.state.buttonExists)? + + : + null + } +
    + } + } + ``` + +1. You'll notice we get a warning because the interval still exists, even though the component does not. Let's create a pointer so that the interval can be cleared + + ```JavaScript + componentDidMount(){ + this.timerID = window.setInterval( + () => { + this.setState({ count: (this.state.count - 1) }); + }, + 1000 + ) + } + ``` + +1. And now clear the interval when the component is destroyed + + ```JavaScript + componentWillUnmount(){ + clearInterval(this.timerID); + } + ``` + +1. Lastly, if some prop/state property is updated on the component, we can perform an action. + + ```JavaScript + componentDidUpdate(previousProps, previousState){ + console.log('This component has changed. The previous count was: ' + previousState.count); + } + ``` + +## Style a component + +You can use CSS as normal, but if you want to stick with React's component based architecture, you can use JavaScript to style a component without leaving the file + +```JavaScript +let basicStyles = { + background:'red', + borderWidth:'2px', + borderStyle:'solid', + borderColor:'black' +}; + +class AwesomeSection extends React.Component { + render(){ + return
    + sure is awesome +
    + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` + +You can extend your styles with `Object.assign()` + +```JavaScript +let basicStyles = { + background:'red', + borderWidth:'2px', + borderStyle:'solid', + borderColor:'black' +}; + +let awesomeStyles = Object.assign({}, basicStyles, { padding: '10px'}) + +class AwesomeSection extends React.Component { + render(){ + return
    + sure is awesome +
    + } +} +``` + +You could also use composition to mimic inheritance: + +```JavaScript +let basicStyles = { + background:'red', + borderWidth:'2px', + borderStyle:'solid', + borderColor:'black' +}; + +class BasicSection extends React.Component { + render(){ + return
    + {this.props.children} +
    + } +} + +let awesomeStyles = { padding: '10px'}; + +class AwesomeSection extends React.Component { + render(){ + return + sure is awesome + + } +} + +ReactDOM.render( + , + document.querySelector('main') +); +``` diff --git a/ReactRouter.md b/ReactRouter.md new file mode 100755 index 0000000..a64d808 --- /dev/null +++ b/ReactRouter.md @@ -0,0 +1,252 @@ +![In Rhythm](logo.gif) + +# React router + +## Lesson Objectives + +1. Basic Install +1. Get React Working +1. Install React Router +1. Create a Link To a Route +1. Create a Router Handler +1. Create a Parameterized Router Handler + +## Basic Install + +``` +npm init +touch .gitignore +``` + +In `.gitignore` add + +``` +node_modules +``` + +Install basic packages: + +``` +npm install babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom webpack webpack-dev-server --save-dev +``` + +Create `webpack.config.js` + +``` +touch webpack.config.js +``` + +And set up a standard config: + +```javascript +module.exports = { + entry: './js/index.js', + output: { + filename: 'dist/bundle.js' + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + use: { + loader: 'babel-loader', + options: { + presets: ["es2015", "react"] + } + } + } + ] + } +}; +``` + +In `package.json`, tell it to use webpack as a build command: + +```javascript +"scripts": { + "build": "webpack-dev-server --open" +}, +``` + +## Get React Working + +Create index.html: + +``` +touch index.html +``` + +Write basic HTML setup code: + +```html + + + + + + + +

    Stuff

    + + +``` + +Create a `js` dir and a `js/index.js` file + +``` +mkdir js +touch js/index.js +``` + +In `js/index.js` write some basic react code: + +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; + +ReactDOM.render( +

    Matt is amazing

    , + document.querySelector('main') +); +``` + +In `index.html` include the soon-to-be compiled js and a `
    ` tag for React: + +```html + + + + + + + +

    Stuff

    +
    + + + +``` + +Build and run: + +``` +npm run build +``` + +## Install React Router + +Install the `react-router-dom` package + +``` +npm install react-router-dom --save-dev +``` + +Import various components in `js/index.js`: + +```javascript +import { + BrowserRouter as Router, + Route, + Link +} from 'react-router-dom' +``` + +In `js/index.js`, wrap your base tag in a `` component: + +```javascript +ReactDOM.render( + +

    Matt is amazing

    +
    , + document.querySelector('main') +); +``` + +## Create a Link To a Route + +In `js/index.js` create `` components: + +```javascript +ReactDOM.render( + +
      +
    • + About +
    • +
    +
    , + document.querySelector('main') +); +``` + +## Create a Router Handler + +In `js/index.js` create a component to show for the `/about` route: + +```javascript +class About extends React.Component{ + render(){ + return

    About

    + } +} +``` + +In `js/index.js` create a handler that will place that component when the url is correct: + +```javascript +ReactDOM.render( + +
    +
      +
    • + About +
    • +
    + +
    +
    , + document.querySelector('main') +); +``` + +## Create a Parameterized Router Handler + +In `js/index.js`, create `Link` to `/topics/news`: + +```html + +``` + +In `js/index.js`, create a route handler that handles `/topics/:topicname`, where `:topicname` is url param that can be anything: + +```html +
    +
      +
    • + About +
    • +
    • + Topics +
    • +
    + + +
    +``` + +In `js/index.js`, create the `Topics` component that will be placed when the url is appropriate: + +```javascript +class Topics extends React.Component{ + render(){ + return

    Topics: {this.props.match.params.topicname}

    + } +} +``` diff --git a/Redux.md b/Redux.md new file mode 100755 index 0000000..679a48c --- /dev/null +++ b/Redux.md @@ -0,0 +1,205 @@ +![In Rhythm](logo.gif) + +# Intro to Redux + +## Lesson Objectives + +1. Describe Redux +1. Install Redux +1. Create a reducer +1. Create a subscriber +1. Dispatch some actions +1. Move the store to a separate file + +## Describe Redux + +- Redux acts as a third party that just deals with data so that the "state" of a model (e.g. a list of comments) doesn't have to be constantly passed around between react components +- Redux works on the publisher/subscriber model + - a subscriber subscribes to Redux + - any subscribers to Redux receive notification when Redux's data changes + - the subscribers can then do what they want with the new "state" of Redux + +## Install Redux + +Initialize the directory: + +``` +npm init +``` + +Create a main JS file: + +``` +touch index.js +``` + +Install Webpack: + +``` +npm install webpack --save-dev +``` + +Install Redux: + +``` +npm install redux --save-dev +``` + +Create a config for Webpack: + +``` +touch webpack.config.js +``` + +Add appropriate config code: + +```javascript +module.exports = { + entry: './index.js', + output: { + filename: 'bundle.js' + } +}; +``` + +Set up NPM to run Webpack: + +```javascript +"scripts": { + "build": "webpack" +}, +``` + +Have `index.js` import `createStore` from `redux` + +```javascript +import { createStore } from 'redux'; + +console.log(createStore); +``` + +Test that import works: + +``` +npm run build +``` + +## Create a reducer + +A reducer will take an action and tell Redux how to alter its state. For now, write one in `index.js` + +```javascript +import { createStore } from 'redux'; + +const comments = function(state = [], action){ //this function must take these params + switch(action.type){ //action must be an object that has a "type" property + case 'ADD': //if the type of action is "ADD" + return [...state, action.comment]; //return a copy of Redux's current state with the added comment + default: //always have a default that returns the current state + return state + } +} + +//now create a data store based on the custom reducer +const store = createStore(comments); +``` + +## Create a subscriber + +- Now let's create a subscriber in `index.js` that will be notified when our data store changes +- In the real world version of this example, this would probably be an HTML list that would update each time a new comment is made + +```javascript +//subscriber (e.g. an HTML list) +store.subscribe(function(){ + console.log(store.getState()); //for now just log what's in the store +}); +``` + +## Dispatch some actions + +- In `index.js`, let's dispatch some actions, telling Redux how to change state: +- In the real world version of this example, this would probably be a user filling out an HTML form and submitting it + +```javascript +//publisher (e.g. an HTML form) +store.dispatch({ + type:'ADD', + comment: { + body:'this rox', + author:'bob' + } +}); +store.dispatch({ + type:'ADD', + comment: { + body:'no, you rox', + author:'sally' + } +}); +store.dispatch({ + type:'ADD', + comment: { + body:'we all rox', + author:'zagthor' + } +}); +``` + +## Move the store to a separate file + +We want our data store in a separate file from our view logic + +``` +touch store.js +``` + +In `store.js`: + +```javascript +import { createStore } from 'redux': + +const comments = function(state = [], action){ + switch(action.type){ + case 'ADD': + return [...state, action.comment]; + default: + return state + } +} +export default createStore(comments); +``` + +Import the store in `index.js`: + +```javascript +import store from './store.js'; + +//subscriber (e.g. an HTML list) +store.subscribe(function(){ + console.log(store.getState()) +}); + +//publisher (e.g. an HTML form) +store.dispatch({ + type:'ADD', + comment: { + body:'this rox', + author:'bob' + } +}); +store.dispatch({ + type:'ADD', + comment: { + body:'no, you rox', + author:'sally' + } +}); +store.dispatch({ + type:'ADD', + comment: { + body:'we all rox', + author:'zagthor' + } +}); +``` diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100755 index 0000000..f6bf9fa --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,13 @@ +# Summary + +* [Intro To ES6](/ES6.md) +* [Essential Tools (Atom/JSX + Babel.io)](/Tools.md) +* [React Basics](/ReactBasics.md) +* [Essential Tools (React Dev Tools)](/Tools.md) +* [Webpack](/Webpack.md) +* [Comments Component Build](/Comments.md) +* [Intro To Redux](/Redux.md) +* [Integrate Redux Into Comments](/CommentsRedux.md) +* [Integrate Redux Into Comments Part 2](/CommentsRedux2.md) +* [AJAX And Redux](/AJAXRedux.md) +* [React Router](/ReactRouter.md) diff --git a/Tools.md b/Tools.md new file mode 100755 index 0000000..6493462 --- /dev/null +++ b/Tools.md @@ -0,0 +1,44 @@ +![In Rhythm](logo.gif) + +# Tools + +## Lesson Objectives + +1. Make Atom recognize JSX +1. Transpile ES2015 to ES5 Using Babel.io +1. Debug React Using React Dev Tools + +## Make Atom recognize JSX + +- By default, text editors like Atom don't recognize JSX code +- Atom allows us to install packages to enhance it + +To Install from Atom: + +1. Go to `Atom->Preferences` +1. Click `+ Install` +1. Search for `"react"` +1. Click `Install` + +To Install from Terminal: + +``` +apm install react +``` + +You might need to do `Atom->Install Shell Commands` + +## Transpile ES2015 to ES5 Using Babel.io + +- Babel is an NPM package that converts ES2015 to ES5 +- See what it does at: https://babeljs.io/repl/ +- You can run it from the command line, but it's more common to use it as a plug-in to Webpack + +## Debug React Using React Dev Tools + +The [React dev tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en-US) allow you to inspect elements and see their properties, etc. + +![react dev tools](https://lh3.googleusercontent.com/GjX6Q3_FVJfc0DqE2wiPKkgOfth6otzV-D7GV-wB6sH5_t1oodMaHOBLsYOLeydb85bKWu6X=s1280-h800-e365-rw) + +- Follow [this link](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en-US) and click "Add To Chrome" +- You may need to refresh/restart chrome diff --git a/Webpack.md b/Webpack.md new file mode 100755 index 0000000..c109744 --- /dev/null +++ b/Webpack.md @@ -0,0 +1,339 @@ +![In Rhythm](logo.gif) + +# Webpack + +- Webpack assembles your code and uses plug-ins to add additional features +- The only thing it does by itself is deal with ES2015 import/export statements + +## Lesson Objectives + +1. Install/Run Webpack +1. Create default export +1. Create a named export +1. Import only certain exports of file +1. Run webpack with NPM +1. Run webpack with Babel +1. Run webpack with JSX +1. Run webpack dev server + +## Install/Run Webpack + +``` +npm init +npm install webpack --save-dev +``` + +Create a test file: + +``` +mkdir js +touch js/index.js +``` + +Add basic log to `index.js`: + +```javascript +console.log('hi'); +``` + +Create a `dist` folder: + +``` +mkdir dist +``` + +We can now run: + +``` +./node_modules/.bin/webpack js/index.js dist/bundle.js +``` + +Create a basic HTML file (`index.html`): + +```html + + + + + + + + + + + +``` + +Run it in the browser: + +``` +open index.html +``` + +## Create default export + +Create a fake library (`js/mylib.js`) + +```javascript +const myFunc = function(){ + console.log('included!'); +} + +export default myFunc; +``` + +include it in `js/index.js` + +```javascript +import defaultImport from './mylib.js'; +defaultImport(); +``` + +## Create a named export + +Create the export in `js/mylib.js`: + +```javascript +export const myFunc2 = function(){ + console.log('also included'); +} +``` + +**NOTE** The export can be defined when the export is created. This can happen for default and named exports + +Import the function in `js/index.js`: + +```javascript +import defaultImport, {myFunc2 as renamedExport} from './mylib.js'; +defaultImport(); +renamedExport(); +``` + +If you don't want to rename the import in `js/index.js`: + +```javascript +import defaultImport, {myFunc2} from './mylib.js'; +defaultImport(); +myFunc2(); +``` + +We can of course set exports to variables in `js/mylib.js`: + +```javascript +const savedFunc = function(){ + console.log('also included'); +} + +export {savedFunc as myFunc2} +``` + +If the variable name is the same as the export name: + +```javascript +const myFunc2 = function(){ + console.log('also included'); +} + +export {myFunc2} +``` + +## Import only certain exports of file + +You can have multiple named exports in `js/mylib.js`: + +```javascript +const unnecessary = function(){ + console.log('unnecessary'); +} + +export { + myFunc2, + unnecessary as omitme +} +``` + +If you don't import them in `js/index.js`, they won't work: + +```javascript +import defaultImport, {myFunc2} from './mylib.js'; +defaultImport(); +myFunc2(); +omitme(); +``` + +## Run webpack with NPM + +Create `webpack.config.js` to simplify the terminal command + +```javascript +module.exports = { + entry: './js/index.js', + output: { + filename: 'dist/bundle.js' + } +}; +``` + +Now we can run: + +``` +./node_modules/.bin/webpack +``` + +We can put this command in our `package.json` file: + +```javascript +"scripts": { + "build": "webpack" +}, +``` + +and run + +``` +npm run build +``` + +## Run webpack with Babel + +First install the core transpiler, the loader which loads babel-plugins, and the es2015 plugin for babel: + +``` +npm install babel-core babel-loader babel-preset-es2015 --save-dev +``` + +Modify the webpack config to use these: + +```javascript +module.exports = { + entry: './js/index.js', + output: { + filename: 'dist/bundle.js' + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, //for all files that end with js/jsx + use: { + loader: 'babel-loader', //use the babel loader to load: + options: { + presets: ["es2015"] //the es2015 compiler + } + } + } + ] + } +}; +``` + +Now use some ES2015 syntax in `js/index.js`: + +```javascript +class Foo { + bar(){ + console.log('classes!'); + } +} +``` + +The result in `dist/build.js` is the ES5 version: + +```javascript +var Foo = function () { + function Foo() { + _classCallCheck(this, Foo); + } + + _createClass(Foo, [{ + key: 'bar', + value: function bar() { + console.log('classes!'); + } + }]); + + return Foo; +}(); +``` + +## Run webpack with JSX + +Install react core, the dom manipulator, and the babel transpiler plugin: + +``` +npm install react react-dom babel-preset-react --save-dev +``` + +In `webpack.config.js` use the `babel-loader` to load the `react` plugin: + +```javascript +use: { + loader: 'babel-loader', + options: { + presets: ["es2015", "react"] + } +} +``` + +Add a `main` tag and move our `script` tag to the bottom of `index.html` + +```html + + + + + + + +
    + + + +``` + +Write some react in `js/index.js`: + +```javascript +import ReactDOM from 'react-dom'; //import react-dom from npm install dir +import React from 'react'; //import react from npm install dir + +ReactDOM.render( +

    Hello, world!

    , + document.querySelector('main') +); +``` + +## Run webpack dev server + +Webpack dev server is an "all-in-one" package that + +- provides a basic static file server that serves your front-end static html/css/js files +- watches your files and runs webpack when any of them change +- reloads your browser whenever webpack is run + +Install it: + +``` +npm install webpack-dev-server --save-dev +``` + +Alter `package.json` to use webpack-dev-server instead of webpack + +```javascript +"scripts": { + "build": "webpack-dev-server --open" +}, +``` + +Now you can run + +``` +npm run build +``` + +and it will open up your browser automatically. Change a file, and see it reload. + +**NOTE** Webpack doesn't actually write the transpiled files to disk. Instead it serves them from memory. You can delete the `dist` directory + +``` +rm -r dist +```