React: Difference between revisions
add personal blogs to references |
|||
(16 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
Notes from Learning React, 2nd Edition by Porcello & Banks | Notes from '''Learning React, 2nd Edition''' by Porcello & Banks and other sources | ||
= Installation = | = Installation = | ||
Line 86: | Line 86: | ||
== Arrow functions and scope == | == Arrow functions and scope == | ||
Reference | |||
* [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions Arrow functions] | |||
* [https://www.codementor.io/dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc Understanding "this" in javascript with arrow functions] | |||
<source lang="js"> | <source lang="js"> | ||
Line 116: | Line 123: | ||
tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver | tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver | ||
</source> | </source> | ||
== Destructing objects == | == Destructing objects == | ||
Line 259: | Line 265: | ||
== Spread operator == | == Spread operator == | ||
Reference: [https://github.com/tc39/proposal-object-rest-spread Object Rest/Spread Properties for ECMAScript] | |||
<source lang="js"> | <source lang="js"> | ||
Line 327: | Line 335: | ||
// } | // } | ||
</source> | </source> | ||
== Asynchronous requests == | == Asynchronous requests == | ||
Line 1,181: | Line 1,188: | ||
</source> | </source> | ||
=== Composition === | |||
chaining | |||
<source lang="js"> | |||
const template = "hh:mm:ss tt"; | |||
const clockTime = template | |||
.replace("hh", "03") | |||
.replace("mm", "33") | |||
.replace("ss", "33") | |||
.replace("tt", "PM"); | |||
console.log(clockTime); | |||
// "03:33:33 PM" | |||
</source> | |||
<code>both</code> function pipes a value through two separate functions. | |||
<source lang="js"> | |||
const both = date => appendAMPM(civilianHours(date)); | |||
// but hard to read, so better approach is to use higher order function | |||
const compose = (...fns) => arg => | |||
fns.reduce((composed, f) => f(composed), arg); | |||
const both = compose( | |||
civilianHours, | |||
appendAMPM | |||
); | |||
both(new Date()); | |||
</source> | |||
== Putting it all together == | |||
ticking time | |||
<source lang="js"> | |||
// Log Clock Time every Second | |||
setInterval(logClockTime, 1000); | |||
function logClockTime() { | |||
// Get Time string as civilian time | |||
let time = getClockTime(); | |||
// Clear the Console and log the time | |||
console.clear(); | |||
console.log(time); | |||
} | |||
function getClockTime() { | |||
// Get the Current Time | |||
let date = new Date(); | |||
let time = ""; | |||
// Serialize clock time | |||
let time = { | |||
hours: date.getHours(), | |||
minutes: date.getMinutes(), | |||
seconds: date.getSeconds(), | |||
ampm: "AM" | |||
}; | |||
// Convert to civilian time | |||
if (time.hours == 12) { | |||
time.ampm = "PM"; | |||
} else if (time.hours > 12) { | |||
time.ampm = "PM"; | |||
time.hours -= 12; | |||
} | |||
// Prepend a 0 on the hours to make double digits | |||
if (time.hours < 10) { | |||
time.hours = "0" + time.hours; | |||
} | |||
// prepend a 0 on the minutes to make double digits | |||
if (time.minutes < 10) { | |||
time.minutes = "0" + time.minutes; | |||
} | |||
// prepend a 0 on the seconds to make double digits | |||
if (time.seconds < 10) { | |||
time.seconds = "0" + time.seconds; | |||
} | |||
// Format the clock time as a string "hh:mm:ss tt" | |||
return time.hours + ":" + time.minutes + ":" + time.seconds + " " + time.ampm; | |||
} | |||
</source> | |||
REFACTORING using what we learned | |||
in functional programs, use functions over values wherever possible; invoke the function to obtain the value when needed | |||
<source lang="js"> | |||
const oneSecond = () => 1000; | |||
const getCurrentTime = () => new Date(); | |||
const clear = () => console.clear(); | |||
const log = message => console.log(message); | |||
</source> | |||
three functions will be used to mutate the clock | |||
# serializeClockTime - take a date object and returns a object for clock time that contains hours minutes, and seconds. | |||
# civilianHours - takes the clock time object and returns an object where hours are converted to civilian time (1300 becomes 1:00) | |||
# appendAMPM - take the clock time object and appends time of day, AM or PM, to that object. | |||
<source lang="js"> | |||
const serializeClockTime = date => ({ | |||
hours: date.getHours(), | |||
minutes: date.getMinutes(), | |||
seconds: date.getSeconds() | |||
}); | |||
const civilianHours = clockTime => ({ | |||
...clockTime, | |||
hours: clockTime.hours > 12 ? clockTime.hours - 12 : clockTime.hours | |||
}); | |||
const appendAMPM = clockTime => ({ | |||
...clockTime, | |||
ampm: clockTime.hours >= 12 ? "PM" : "AM" | |||
}); | |||
</source> | |||
higher order functions needed | |||
# display - takes a target function and returns a function that will send a time to the target. In this eample the target will be console.log | |||
# formatClock - takes a template string and uses it to return clock time formatted based upon the criteria from the string. | |||
# prependZero - | |||
<source lang="js"> | |||
const display = target => time => target(time); | |||
const formatClock = format => time => | |||
format | |||
.replace("hh", time.hours) | |||
.replace("mm", time.minutes) | |||
.replace("ss", time.seconds) | |||
.replace("tt", time.ampm); | |||
const prependZero = key => clockTime => ({ | |||
...clockTime, | |||
[key]: clockTime[key] < 10 ? "0" + clockTime[key] : clockTime[key] | |||
}); | |||
</source> | |||
compose | |||
<source lang="js"> | |||
const convertToCivilianTime = clockTime => | |||
compose( | |||
appendAMPM, | |||
civilianHours | |||
)(clockTime); | |||
const doubleDigits = civilianTime => | |||
compose( | |||
prependZero("hours"), | |||
prependZero("minutes"), | |||
prependZero("seconds") | |||
)(civilianTime); | |||
const startTicking = () => | |||
setInterval( | |||
compose( | |||
clear, | |||
getCurrentTime, | |||
serializeClockTime, | |||
convertToCivilianTime, | |||
doubleDigits, | |||
formatClock("hh:mm:ss tt"), | |||
display(log) | |||
), | |||
oneSecond() | |||
); | |||
startTicking(); | |||
</source> | |||
= How React Works = | |||
== React elements == | |||
<source lang="js"> | |||
React.createElement("h1", { id: "recipe-0" }, "Baked Salmon"); | |||
// <h1 id="recipe-0">Baked Salmon</h1> | |||
// log: | |||
{ | |||
$$typeof: Symbol(React.element), | |||
"type": "h1", | |||
"key": null, | |||
"ref": null, | |||
"props": {id: "recipe-0", children: "Baked Salmon"}, | |||
"_owner": null, | |||
"_store": {} | |||
} | |||
</source> | |||
== ReactDOM == | |||
contains tools necessary to render React element in the browser via '''render''' method | |||
<source lang="js"> | |||
const dish = React.createElement("h1", null, "Baked Salmon"); | |||
ReactDOM.render(dish, document.getElementById("root")); | |||
</source> | |||
rendering array of elements | |||
<source lang="js"> | |||
const dish = React.createElement("h1", null, "Baked Salmon"); | |||
const dessert = React.createElement("h2", null, "Coconut Cream Pie"); | |||
ReactDOM.render([dish, dessert], document.getElementById("root")); | |||
</source> | |||
== Children == | |||
<source lang="js"> | |||
React.createElement( | |||
"ul", | |||
null, | |||
React.createElement("li", null, "2 lb salmon"), | |||
React.createElement("li", null, "5 sprigs fresh rosemary"), | |||
React.createElement("li", null, "2 tablespoons olive oil"), | |||
React.createElement("li", null, "2 small lemons"), | |||
React.createElement("li", null, "1 teaspoon kosher salt"), | |||
React.createElement("li", null, "4 cloves of chopped garlic") | |||
); | |||
// equivalent to | |||
const items = [ | |||
"2 lb salmon", | |||
"5 sprigs fresh rosemary", | |||
"2 tablespoons olive oil", | |||
"2 small lemons", | |||
"1 teaspoon kosher salt", | |||
"4 cloves of chopped garlic" | |||
]; | |||
React.createElement( | |||
"ul", | |||
{ className: "ingredients" }, | |||
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient)) | |||
); | |||
</source> | |||
== React Components == | |||
<source lang="js"> | |||
function IngredientsList({ items }) { // destructure items from props | |||
return React.createElement( | |||
"ul", | |||
{ className: "ingredients" }, | |||
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient) | |||
); | |||
} | |||
const items = [ | |||
"2 lb salmon", | |||
"5 sprigs fresh rosemary", | |||
"2 tablespoons olive oil", | |||
"2 small lemons", | |||
"1 teaspoon kosher salt", | |||
"4 cloves of chopped garlic" | |||
]; | |||
ReactDOM.render( | |||
React.createElement(IngredientsList, { items }, null), | |||
document.getElementById("root") | |||
); | |||
</source> | |||
use functions to create components because class syntax is being deprecated. | |||
= React with JSX = | |||
== React elements as JSX == | |||
=== Nested components === | |||
<source lang="html"> | |||
<IngredientsList> | |||
<Ingredient /> | |||
<Ingredient /> | |||
<Ingredient /> | |||
</IngredientsList> | |||
</source> | |||
=== ClassName === | |||
Since <code>class</code> is a reserved word in JavaScript, className is used to define the class attribute. | |||
=== JavaScript Expressions === | |||
<source lang="js"> | |||
<h1>{title}</h1> | |||
<input type="checkbox" defaultChecked="{false}" /> | |||
</source> | |||
=== Evaluation === | |||
<source lang="js"> | |||
<h1>{"Hello" + title}</h1> | |||
<h1>{title.toLowerCase().replace}</h1> | |||
function appendTitle({ title }) { | |||
console.log(`${title} is great!`); | |||
} | |||
</source> | |||
=== Mapping Arrays with JSX === | |||
<source lang="js"> | |||
<ul> | |||
{props.ingredients.map((ingredient, i) => ( | |||
<li key="{i}">{ingredient}</li> | |||
))} | |||
</ul> | |||
</source> | |||
== Babel == | |||
template file to use | |||
<source lang="html"> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<title>React Examples</title> | |||
</head> | |||
<body> | |||
<div id="root"></div> | |||
<!-- React Library & React DOM --> | |||
<script src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script> | |||
<script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script> | |||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |||
<script type="text/babel"> | |||
// JSX code here. Or link to separate JavaScript file that contains JSX. | |||
</script> | |||
</body> | |||
</html> | |||
</source> | |||
== Recipes as JSX == | |||
<source lang="js"> | |||
const data = [ | |||
{ | |||
name: "Baked Salmon", | |||
ingredients: [ | |||
{ name: "Salmon", amount: 1, measurement: "l lb" }, | |||
{ name: "Pine Nuts", amount: 1, measurement: "cup" }, | |||
{ name: "Butter Lettuce", amount: 2, measurement: "cups" }, | |||
{ name: "Yellow Squash", amount: 1, measurement: "med" }, | |||
{ name: "Olive Oil", amount: 0.5, measurement: "cup" }, | |||
{ name: "Garlic", amount: 3, measurement: "cloves" } | |||
], | |||
steps: [ | |||
"Preheat the oven to 350 degrees.", | |||
"Spread the olive oil around a glass baking dish.", | |||
"Add the yellow squash and place in the oven for 30 mins.", | |||
"Add the salmon, garlic, and pine nuts to the dish.", | |||
"Bake for 15 minutes.", | |||
"Remove from oven. Add the lettuce and serve." | |||
] | |||
}, | |||
{ | |||
name: "Fish Tacos", | |||
ingredients: [ | |||
{ name: "Whitefish", amount: 1, measurement: "l lb" }, | |||
{ name: "Cheese", amount: 1, measurement: "cup" }, | |||
{ name: "Iceberg Lettuce", amount: 2, measurement: "cups" }, | |||
{ name: "Tomatoes", amount: 2, measurement: "large" }, | |||
{ name: "Tortillas", amount: 3, measurement: "med" } | |||
], | |||
steps: [ | |||
"Cook the fish on the grill until cooked through.", | |||
"Place the fish on the 3 tortillas.", | |||
"Top them with lettuce, tomatoes, and cheese." | |||
] | |||
} | |||
]; | |||
// A function component for an individual Recipe | |||
function Recipe (props) { | |||
... | |||
} | |||
// A function component for the Menu of Recipes | |||
function Menu (props) { | |||
return ( | |||
<article> | |||
<header> | |||
<h1>{props.title}</h1> | |||
</header> | |||
<div className="recipes"> | |||
{props.recipes.map((recipe, i) => ( | |||
<Recipe | |||
key={i} | |||
name={recipe.name} | |||
ingredients={recipe.ingredients} | |||
steps={recipe.steps} | |||
/> | |||
))} | |||
// equivalent to | |||
{ | |||
props.recipes.map((recipe, i) => <Recipe key={i} {...recipe} />); | |||
} | |||
</div> | |||
</article> | |||
); | |||
} | |||
// A call to ReactDOM.render to render our Menu into the current DOM | |||
ReactDOM.render( | |||
<Menu recipes={data} title="Delicious Recipes" />, | |||
document.getElementById("root") | |||
); | |||
</source> | |||
we can access title & recipes vars directly, so vars don't have to be prefixed with props: | |||
<source lang="js"> | |||
function Menu({ title, recipes }) { | |||
return ( | |||
<article> | |||
<header> | |||
<h1>{title}</h1> | |||
</header> | |||
<div className="recipes"> | |||
{recipes.map((recipe, i) => ( | |||
<Recipe key={i} {...recipe} /> | |||
))} | |||
</div> | |||
</article> | |||
); | |||
} | |||
</source> | |||
code for the component for each individual recipe: | |||
<source lang="js"> | |||
function Recipe({ name, ingredients, steps }) { | |||
return ( | |||
<section id={name.toLowerCase().replace(/ /g, "-")}> | |||
<h1>{name}</h1> | |||
<ul className="ingredients"> | |||
{ingredients.map((ingredient, i) => ( | |||
<li key={i}>{ingredient.name}</li> | |||
))} | |||
</ul> | |||
<section className="instructions"> | |||
<h2>Cooking Instructions</h2> | |||
{steps.map((step, i) => ( | |||
<p key={i}>{step}</p> | |||
))} | |||
</section> | |||
</section> | |||
); | |||
} | |||
</source> | |||
== React fragments == | |||
<source lang="js"> | |||
function Cat({ name }) { | |||
return ( | |||
<React.Fragment> | |||
<h1>The cat's name is {name}</h1> | |||
<p>He's good.</p> | |||
</React.Fragment> | |||
); | |||
} | |||
// equivalent to | |||
function Cat({ name }) { | |||
return ( | |||
<> | |||
<h1>The cat's name is {name}</h1> | |||
<p>He's good.</p> | |||
</> | |||
); | |||
} | |||
</source> | |||
== Intro to webpack == | |||
* Code splitting | |||
* Minification | |||
* Feature flagging | |||
* Hot module replacement (HMR) | |||
* Modularity | |||
* Composition | |||
* Speed | |||
* Consistency | |||
Installing webpack | |||
<source lang="console"> | |||
$ npm install --save-dev webpack@next webpack-cli | |||
</source> | |||
Installing Babel dependencies | |||
<source lang="console"> | |||
$ npm install babel-loader @babel/core --save-dev | |||
</source> | |||
Specify Babel presets | |||
<source lang="console"> | |||
$ npm install @babel/preset-env @babel/preset-react --save-dev | |||
</source> | |||
= React State Management = | |||
= Reference = | |||
* [https://react-icons.netlify.com React Icons] | |||
* Personal blogs | |||
** [https://www.leighhalliday.com Leigh Halliday] | |||
** [https://www.robinwieruch.de Robin Wieruch] |
Latest revision as of 08:36, 9 October 2019
Notes from Learning React, 2nd Edition by Porcello & Banks and other sources
Installation
// initialize a nodejs project (creates package.json)
$ npm init -y
// package manager
$ npm install yarn
$ yarn install packagename
$ yarn remove packagename
JavaScript
Three ways to declare variables are
- const
- var
- let
Template string
console.log(`${lastName}, ${firstName} ${middleName}`);
document.body.innerHTML = `
<section>
<header>
<h1>The React Blog</h1>
</header>
<article>
<h2>${article.title}</h2>
${article.body}
</article>
<footer>
<p>copyright ${new Date().getYear()} | The React Blog</p>
</footer>
</section>
`;
Function declaration vs function expression
declarations are hoisted
const f = function() {
};
Arrow functions
const lordify = function(firstName) {
return `${firstName} of Canterbury`;
};
// equals
const lordify = firstName => `${firstName} of Canterbury`;
Returning an object
DON'T FORGET PARENTHESES!
const person = (firstName, lastName) => ({
first: firstName,
last: lastName
});
console.log(person("Flad", "Hanson"));
Arrow functions and scope
Reference
const tahoe = {
mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
print: function(delay = 1000) {
setTimeout(function() {
console.log(this.mountains.join(", "));
}, delay);
}
};
tahoe.print(); // Uncaught TypeError: Cannot read property 'join' of undefined
console.log(this); // Window {}
To solve this problem:
const tahoe = {
mountains: ["Freel", "Rose", "Tallac", "Rubicon", "Silver"],
print: function(delay = 1000) {
setTimeout(() => {
console.log(this.mountains.join(", "));
}, delay);
}
};
tahoe.print(); // Freel, Rose, Tallac, Rubicon, Silver
Destructing objects
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
console.log(bread, meat); // dutch crunch tuna
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
let { bread, meat } = sandwich;
bread = "garlic";
meat = "turkey";
console.log(bread); // garlic
console.log(meat); // turkey
console.log(sandwich.bread, sandwich.meat); // dutch crunch tuna
destructuring parameter
const lordify = ({ firstname }) => {
console.log(`${firstname} of Canterbury`);
};
const regularPerson = {
firstname: "Bill",
lastname: "Wilson"
};
lordify(regularPerson); // Bill of Canterbury
const regularPerson = {
firstname: "Bill",
lastname: "Wilson",
spouse: {
firstname: "Phil",
lastname: "Wilson"
}
};
const lordify = ({ spouse: { firstname } }) => {
console.log(`${firstname} of Canterbury`);
};
lordify(regularPerson); // Phil of Canterbury
Destructuring arrays
const [firstAnimal] = ["Horse", "Mouse", "Cat"];
console.log(firstAnimal); // Horse
const [, , thirdAnimal] = ["Horse", "Mouse", "Cat"];
console.log(thirdAnimal); // Cat
Object literal enhancement
const name = "Tallac";
const elevation = 9738;
const funHike = { name, elevation };
console.log(funHike); // {name: "Tallac", elevation: 9738}
const name = "Tallac";
const elevation = 9738;
const print = function() {
console.log(`Mt. ${this.name} is ${this.elevation} feet tall`);
};
const funHike = { name, elevation, print };
funHike.print(); // Mt. Tallac is 9738 feet tall
old vs. new: object syntax
// Old
var skier = {
name: name,
sound: sound,
powderYell: function() {
var yell = this.sound.toUpperCase();
console.log(`${yell} ${yell} ${yell}!!!`);
},
speed: function(mph) {
this.speed = mph;
console.log("speed:", mph);
}
};
// New
const skier = {
name,
sound,
powderYell() {
let yell = this.sound.toUpperCase();
console.log(`${yell} ${yell} ${yell}!!!`);
},
speed(mph) {
this.speed = mph;
console.log("speed:", mph);
}
};
Spread operator
Reference: Object Rest/Spread Properties for ECMAScript
const peaks = ["Tallac", "Ralston", "Rose"];
const canyons = ["Ward", "Blackwood"];
const tahoe = [...peaks, ...canyons];
console.log(tahoe.join(", ")); // Tallac, Ralston, Rose, Ward, Blackwood
Getting last element:
const peaks = ["Tallac", "Ralston", "Rose"];
const [last] = peaks.reverse();
console.log(last); // Rose
console.log(peaks.join(", ")); // Rose, Ralston, Tallac
Getting the rest of elements:
const lakes = ["Donner", "Marlette", "Fallen Leaf", "Cascade"];
const [first, ...others] = lakes;
console.log(others.join(", ")); // Marlette, Fallen Leaf, Cascade
function parameters:
function directions(...args) {
let [start, ...remaining] = args;
let [finish, ...stops] = remaining.reverse();
console.log(`drive through ${args.length} towns`);
console.log(`start in ${start}`);
console.log(`the destination is ${finish}`);
console.log(`stopping ${stops.length} times in between`);
}
directions("Truckee", "Tahoe City", "Sunnyside", "Homewood", "Tahoma");
combining objects:
const morning = {
breakfast: "oatmeal",
lunch: "peanut butter and jelly"
};
const dinner = "mac and cheese";
const backpackingMeals = {
...morning,
dinner
};
console.log(backpackingMeals);
// {
// breakfast: "oatmeal",
// lunch: "peanut butter and jelly",
// dinner: "mac and cheese"
// }
Asynchronous requests
Simple promises with fetch
fetch("https://api.randomuser.me/?nat=US&results=1")
.then(res => res.json())
.then(json => json.results)
.then(console.log)
.catch(console.error);
Async/Await
const getFakePerson = async () => {
try {
let res = await fetch("https://api.randomuser.me/?nat=US&results=1");
let { results } = res.json();
console.log(results);
} catch (error) {
console.error(error);
}
};
getFakePerson();
Building Promises
const getPeople = count =>
new Promise((resolves, rejects) => {
const api = `https://api.randomuser.me/?nat=US&results=${count}`;
const request = new XMLHttpRequest();
request.open("GET", api);
request.onload = () =>
request.status === 200
? resolves(JSON.parse(request.response).results)
: reject(Error(request.statusText));
request.onerror = err => rejects(err);
request.send();
});
getPeople(5)
.then(members => console.log(members))
.catch(error => console.error(`getPeople failed: ${error.message}`))
);
Classes
prototypical inheritance
function Vacation(destination, length) {
this.destination = destination;
this.length = length;
}
Vacation.prototype.print = function() {
console.log(this.destination + " | " + this.length + " days");
};
const maui = new Vacation("Maui", 7);
maui.print(); // Maui | 7 days
ES2015 way of declaring class
class Vacation {
constructor(destination, length) {
this.destination = destination;
this.length = length;
}
print() {
console.log(`${this.destination} will take ${this.length} days.`);
}
}
const trip = new Vacation("Santiago, Chile", 7);
trip.print(); // Chile will take 7 days.
Simple inheritance
class Expedition extends Vacation {
constructor(destination, length, gear) {
super(destination, length);
this.gear = gear;
}
print() {
super.print();
console.log(`Bring your ${this.gear.join(" and your ")}`);
}
}
const trip = new Expedition("Mt. Whitney", 3, [
"sunglasses",
"prayer flags",
"camera"
]);
trip.print();
// Mt. Whitney will take 3 days.
// Bring your sunglasses and your prayer flags and your camera
ES6 Modules
A module is a peice of reusable code that can easily be incorporated into other JavaScript files without causing variable collisions.
In text-helpers.js, two functions are exported:
export const print(message) => log(message, new Date())
export const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`)
Exporting only one variable from a module using export default
export default new Expedition("Mt. Freel", 2, ['water","snack"]);
Consuming using the import command
import { print, log } from "./text-helpers";
import freel from "./mt-freel";
print("printing a message");
log("logging a message");
freel.print();
scoping under different name:
import { print as p, log as l } from "./text-helpers";
p("printing a message");
l("logging a message");
import everything
import * as fns from './text-helpers`
CommonJS
the module pattern that is supported by all versions of Node, "Modules"
e.g.
const print(message) => log(message, new Date())
const log(message, timestamp) =>
console.log(`${timestamp.toString()}: ${message}`}
module.exports = {print, log}
CommonJS does not support an import statement; modules are mported with the require function
const { log, print } = require("./txt-helpers");
Functional Programming with JavaScript
What it means to be functional
JavaScript functions are first-class citizens -- they can do the same things that variables can do. Functions can represent data. e.g.
var log = function(msg) {
console.log(msg);
}
log("In JS functions are variables");
// equivalent to
const log = msg => { console.log(msg); };
we can add them to objects:
const obj = {
msg: "they can be added to objects like variables",
log(msg) { console.log(msg); }
};
obj.log(obj.msg);
add them to arrays:
const messages = [
"They can be inserted into arrays",
message => console.log(message),
"like variables",
message => console.log(message)
];
messages[1](messages[0]); // They can be inserted into arrays
messages[3](messages[2]); // like variables
send to other functions as arguments:
const insideFn = logger => {
logger("They can be sent to other functions as arguments");
};
insideFn(message => console.log(message));
// They can be sent to other functions as arguments
returned from other functions:
const createScream = function(logger) {
return function(message) {
logger(message.toUpperCase() + "!!!");
};
};
const scream = createScream(message => console.log(message));
scream("functions can be returned from other functions");
scream("createScream returns a function");
scream("scream invokes that returned function");
// FUNCTIONS CAN BE RETURNED FROM OTHER FUNCTIONS!!!
// CREATESCREAM RETURNS A FUNCTION!!!
// SCREAM INVOKES THAT RETURNED FUNCTION!!!
// equivalent to (using arrows)
const createScream = logger => message => {
logger(message.toUpperCase() + "!!!");
};
// when more than one arrows exist, there's a higher-order function
</source>
Imperative versus declarative
// making a string URL friendly
// IMPERATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = "";
for (var i = 0; i < string.length; i++) {
if (string[i] === " ") {
urlFriendly += "-";
} else {
urlFriendly += string[i];
}
}
console.log(urlFriendly); // "Restaurants-in-Hanalei"
// DECLARATIVE way
const string = "Restaurants in Hanalei";
const urlFriendly = string.replace(/ /g, "-");
console.log(urlFriendly);
Reference: Declarative Programming wiki -- more about d/p paradigm
Declaring DOM example
// imperative approach
const target = document.getElementById("target");
const wrapper = document.createElement("div");
const headline = document.createElement("h1");
wrapper.id = "welcome";
headline.innerText = "Hello World";
wrapper.appendChild(headline);
target.appendChild(wrapper);
// declarative approach using a React component
const { render } = ReactDOM;
const Welcome = () => (
<div id="welcome">
<h1>Hello World</h1>
</div>
);
render(<Welcome />, document.getElementById("target"));
Functional concepts
core concepts: immutability, purity, data transformation, higher-order functions, and recursion
Immutability
data mutation
let color_lawn = {
title: "lawn",
color: "#00ff00",
rating: 0
};
function rateColor(color, rating) {
color.rating = rating;
return color;
}
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 5
in JS, function arguments are references to the actual data; we can rewrite so it does not harm the original data
const rateColor = function(color, rating) {
return Object.assign({}, color, { rating: rating }); // take a blank object, copy copy to that object, and overwrite rating on the copy
};
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 4
// use the spread operator to copy the color into a new object and then overwrite its rating:
// equivalent to
const rateColor = (color, rating) => ({
...color,
rating
});
adding elements to an array
let list = [{ title: "Rad Red" }, { title: "Lawn" }, { title: "Party Pink" }];
const addColor = function(title, colors) {
colors.push({ title: title });
return colors;
};
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 4
// use Array.concat to keep immutable
const addColor = (title, array) => array.concat({ title });
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 3
// equivalent using spread operator
// copies the original list to a new array and then adds a new object containing the color's title to that copy.
const addColor = (title, list) => [...list, { title }];
Pure Functions
function that returns a value that is computed based on its argument; treats arguments as immutable data, so nothing else is changed about the application
e.g. impure function
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
function selfEducate() {
frederick.canRead = true;
frederick.canWrite = true;
return frederick;
}
selfEducate();
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// still the same result
const selfEducate = person => {
person.canRead = true;
person.canWrite = true;
return person;
};
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: true, canWrite: true}
have this function take an argument (now pure)
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
});
console.log(selfEducate(frederick));
console.log(frederick);
// {name: "Frederick Douglass", canRead: true, canWrite: true}
// {name: "Frederick Douglass", canRead: false, canWrite: false}
an impure function --it changes DOM
function Header(text) {
let h1 = document.createElement("h1");
h1.innerText = text;
document.body.appendChild(h1);
}
Header("Header() caused side effects");
In React, UI is expressed with pure functions. e.g.
const Header = props => <h1>{props.title}</h1>;
Guideline for writing pure functions
- at least one argument
- return a value or another function
- should not change or mutate any of its arguments
Data Transformations
via Array.map and Array.reduce
using Array.join
const schools = ["Yorktown", "Washington & Lee", "Wakefield"];
console.log(schools.join(", "));
// "Yorktown, Washington & Lee, Wakefield"
create a new array of the schools that begin with the letter "W"
const wSchools = schools.filter(school => school[0] === "W");
console.log(wSchools);
// ["Washington & Lee", "Wakefield"]
when removing an item from an array, use Array.filter over .pop() or .splice() because .filter() is immutable
const cutSchool = (cut, list) => list.filter(school => school !== cut);
console.log(cutSchool("Washington & Lee", schools).join(", "));
// "Yorktown, Wakefield"
console.log(schools.join("\n"));
// Yorktown
// Washington & Lee
// Wakefield
Array.map takes a function as its argument which will be invoked once for every item in the array, and whatever it returns will be added to the new array
const highSchools = schools.map(school => `${school} High School`);
console.log(highSchools.join("\n"));
// Yorktown High School
// Washington & Lee High School
// Wakefield High School
console.log(schools.join("\n"));
// Yorktown
// Washington & Lee
// Wakefield
e.g. return an object for every school
const highSchools = schools.map(school => ({ name: school }));
console.log(highSchools);
// [
// { name: "Yorktown" },
// { name: "Washington & Lee" },
// { name: "Wakefield" }
// ]
pure f(x) that changes one object in an array of objects
let schools = [
{ name: "Yorktown" },
{ name: "Stratford" },
{ name: "Washington & Lee" },
{ name: "Wakefield" }
];
const editName = (oldName, name, arr) =>
arr.map(item => {
if (item.name === oldName) {
return {
...item,
name
};
} else {
return item;
}
});
let updatedSchools = editName("Stratford", "HB Woodlawn", schools);
console.log(updatedSchools[1]); // { name: "HB Woodlawn" }
console.log(schools[1]); // { name: "Stratford" }
// equivalent to
const editName = (oldName, name, arr) =>
arr.map(item => (item.name === oldName ? { ...item, name } : item));
transform schools object into an array of schools:
const schools = {
Yorktown: 10,
"Washington & Lee": 2,
Wakefield: 5
};
const schoolArray = Object.keys(schools).map(key => ({
name: key,
wins: schools[key]
}));
console.log(schoolArray);
// [
// {
// name: "Yorktown",
// wins: 10
// },
// {
// name: "Washington & Lee",
// wins: 2
// },
// {
// name: "Wakefield",
// wins: 5
// }
// ]
reduce
and reduceRight
are used to trasnform an array into any value, including a number, string, boolean, object, or even a function
maximum number in an array of numbers
const ages = [21, 18, 42, 40, 64, 63, 34];
const maxAge = ages.reduce((max, age) => {
console.log(`${age} > ${max} = ${age > max}`);
if (age > max) {
return age;
} else {
return max;
}
}, 0);
console.log("maxAge", maxAge);
// 21 > 0 = true
// 18 > 21 = false
// 42 > 21 = true
// 40 > 42 = false
// 64 > 42 = true
// 63 > 64 = false
// 34 > 64 = false
// maxAge 64
// equivalent to
const max = ages.reduce((max, value) => (value > max ? value : max), 0);
reduce takes two arguments: a callback function & an original value
reduceRight simply starts reducing from the end of the array rather than the beginning
transform an array into an object
const colors = [
{
id: "xekare",
title: "rad red",
rating: 3
},
{
id: "jbwsof",
title: "big blue",
rating: 2
},
{
id: "prigbj",
title: "grizzly grey",
rating: 5
},
{
id: "ryhbhsl",
title: "banana",
rating: 1
}
];
const hashColors = colors.reduce((hash, { id, title, rating }) => {
hash[id] = { title, rating };
return hash;
}, {});
console.log(hashColors);
// {
// "xekare": {
// title:"rad red",
// rating:3
// },
// "jbwsof": {
// title:"big blue",
// rating:2
// },
// "prigbj": {
// title:"grizzly grey",
// rating:5
// },
// "ryhbhsl": {
// title:"banana",
// rating:1
// }
// }
reducing an array with multiple instances of the same value to an array of unique values
const colors = ["red", "red", "green", "blue", "green"];
const uniqueColors = colors.reduce(
(unique, color) =>
unique.indexOf(color) !== -1 ? unique : [...unique, color],
[]
);
console.log(uniqueColors);
// ["red", "green", "blue"]
Higher-Order Functions
functions that can manipulate other functions; can take functions in as arguments, or return functions, or both
e.g.
const invokeIf = (condition, fnTrue, fnFalse) =>
condition ? fnTrue() : fnFalse();
const showWelcome = () => console.log("Welcome!!!");
const showUnauthorized = () => console.log("Unauthorized!!!");
invokeIf(true, showWelcome, showUnauthorized); // "Welcome!!!"
invokeIf(false, showWelcome, showUnauthorized); // "Unauthorized!!!"
e.g. currying
const userLogs = userName => message =>
console.log(`${userName} -> ${message}`);
const log = userLogs("grandpa23");
log("attempted to load 20 fake members");
getFakeMembers(20).then(
members => log(`successfully loaded ${members.length} members`),
error => log("encountered an error loading members")
);
// grandpa23 -> attempted to load 20 fake members
// grandpa23 -> successfully loaded 20 members
// grandpa23 -> attempted to load 20 fake members
// grandpa23 -> encountered an error loading members
Recursion
const countdown = (value, fn) => {
fn(value);
return value > 0 ? countdown(value - 1, fn) : value;
};
countdown(10, value => console.log(value));
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1
// 0
recursion is well-suited for searching through data structures
const dan = {
type: "person",
data: {
gender: "male",
info: {
id: 22,
fullname: {
first: "Dan",
last: "Deacon"
}
}
}
};
const deepPick = (fields, object = {}) => {
const [first, ...remaining] = fields.split(".");
return remaining.length
? deepPick(remaining.join("."), object[first])
: object[first];
};
deepPick("type", dan); // "person"
deepPick("data.info.fullname.first", dan); // "Dan"
deepPick("data.info.fullname.first", dan); // "Deacon"
// First Iteration
// first = "data"
// remaining.join(".") = "info.fullname.first"
// object[first] = { gender: "male", {info} }
// Second Iteration
// first = "info"
// remaining.join(".") = "fullname.first"
// object[first] = {id: 22, {fullname}}
// Third Iteration
// first = "fullname"
// remaining.join("." = "first"
// object[first] = {first: "Dan", last: "Deacon" }
// Finally...
// first = "first"
// remaining.length = 0
// object[first] = "Deacon"
Composition
chaining
const template = "hh:mm:ss tt";
const clockTime = template
.replace("hh", "03")
.replace("mm", "33")
.replace("ss", "33")
.replace("tt", "PM");
console.log(clockTime);
// "03:33:33 PM"
both
function pipes a value through two separate functions.
const both = date => appendAMPM(civilianHours(date));
// but hard to read, so better approach is to use higher order function
const compose = (...fns) => arg =>
fns.reduce((composed, f) => f(composed), arg);
const both = compose(
civilianHours,
appendAMPM
);
both(new Date());
Putting it all together
ticking time
// Log Clock Time every Second
setInterval(logClockTime, 1000);
function logClockTime() {
// Get Time string as civilian time
let time = getClockTime();
// Clear the Console and log the time
console.clear();
console.log(time);
}
function getClockTime() {
// Get the Current Time
let date = new Date();
let time = "";
// Serialize clock time
let time = {
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds(),
ampm: "AM"
};
// Convert to civilian time
if (time.hours == 12) {
time.ampm = "PM";
} else if (time.hours > 12) {
time.ampm = "PM";
time.hours -= 12;
}
// Prepend a 0 on the hours to make double digits
if (time.hours < 10) {
time.hours = "0" + time.hours;
}
// prepend a 0 on the minutes to make double digits
if (time.minutes < 10) {
time.minutes = "0" + time.minutes;
}
// prepend a 0 on the seconds to make double digits
if (time.seconds < 10) {
time.seconds = "0" + time.seconds;
}
// Format the clock time as a string "hh:mm:ss tt"
return time.hours + ":" + time.minutes + ":" + time.seconds + " " + time.ampm;
}
REFACTORING using what we learned
in functional programs, use functions over values wherever possible; invoke the function to obtain the value when needed
const oneSecond = () => 1000;
const getCurrentTime = () => new Date();
const clear = () => console.clear();
const log = message => console.log(message);
three functions will be used to mutate the clock
- serializeClockTime - take a date object and returns a object for clock time that contains hours minutes, and seconds.
- civilianHours - takes the clock time object and returns an object where hours are converted to civilian time (1300 becomes 1:00)
- appendAMPM - take the clock time object and appends time of day, AM or PM, to that object.
const serializeClockTime = date => ({
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds()
});
const civilianHours = clockTime => ({
...clockTime,
hours: clockTime.hours > 12 ? clockTime.hours - 12 : clockTime.hours
});
const appendAMPM = clockTime => ({
...clockTime,
ampm: clockTime.hours >= 12 ? "PM" : "AM"
});
higher order functions needed
- display - takes a target function and returns a function that will send a time to the target. In this eample the target will be console.log
- formatClock - takes a template string and uses it to return clock time formatted based upon the criteria from the string.
- prependZero -
const display = target => time => target(time);
const formatClock = format => time =>
format
.replace("hh", time.hours)
.replace("mm", time.minutes)
.replace("ss", time.seconds)
.replace("tt", time.ampm);
const prependZero = key => clockTime => ({
...clockTime,
[key]: clockTime[key] < 10 ? "0" + clockTime[key] : clockTime[key]
});
compose
const convertToCivilianTime = clockTime =>
compose(
appendAMPM,
civilianHours
)(clockTime);
const doubleDigits = civilianTime =>
compose(
prependZero("hours"),
prependZero("minutes"),
prependZero("seconds")
)(civilianTime);
const startTicking = () =>
setInterval(
compose(
clear,
getCurrentTime,
serializeClockTime,
convertToCivilianTime,
doubleDigits,
formatClock("hh:mm:ss tt"),
display(log)
),
oneSecond()
);
startTicking();
How React Works
React elements
React.createElement("h1", { id: "recipe-0" }, "Baked Salmon");
// <h1 id="recipe-0">Baked Salmon</h1>
// log:
{
$$typeof: Symbol(React.element),
"type": "h1",
"key": null,
"ref": null,
"props": {id: "recipe-0", children: "Baked Salmon"},
"_owner": null,
"_store": {}
}
ReactDOM
contains tools necessary to render React element in the browser via render method
const dish = React.createElement("h1", null, "Baked Salmon");
ReactDOM.render(dish, document.getElementById("root"));
rendering array of elements
const dish = React.createElement("h1", null, "Baked Salmon");
const dessert = React.createElement("h2", null, "Coconut Cream Pie");
ReactDOM.render([dish, dessert], document.getElementById("root"));
Children
React.createElement(
"ul",
null,
React.createElement("li", null, "2 lb salmon"),
React.createElement("li", null, "5 sprigs fresh rosemary"),
React.createElement("li", null, "2 tablespoons olive oil"),
React.createElement("li", null, "2 small lemons"),
React.createElement("li", null, "1 teaspoon kosher salt"),
React.createElement("li", null, "4 cloves of chopped garlic")
);
// equivalent to
const items = [
"2 lb salmon",
"5 sprigs fresh rosemary",
"2 tablespoons olive oil",
"2 small lemons",
"1 teaspoon kosher salt",
"4 cloves of chopped garlic"
];
React.createElement(
"ul",
{ className: "ingredients" },
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient))
);
React Components
function IngredientsList({ items }) { // destructure items from props
return React.createElement(
"ul",
{ className: "ingredients" },
items.map((ingredient, i) => React.createElement("li", { key: i }, ingredient)
);
}
const items = [
"2 lb salmon",
"5 sprigs fresh rosemary",
"2 tablespoons olive oil",
"2 small lemons",
"1 teaspoon kosher salt",
"4 cloves of chopped garlic"
];
ReactDOM.render(
React.createElement(IngredientsList, { items }, null),
document.getElementById("root")
);
use functions to create components because class syntax is being deprecated.
React with JSX
React elements as JSX
Nested components
<IngredientsList>
<Ingredient />
<Ingredient />
<Ingredient />
</IngredientsList>
ClassName
Since class
is a reserved word in JavaScript, className is used to define the class attribute.
JavaScript Expressions
<h1>{title}</h1>
<input type="checkbox" defaultChecked="{false}" />
Evaluation
<h1>{"Hello" + title}</h1>
<h1>{title.toLowerCase().replace}</h1>
function appendTitle({ title }) {
console.log(`${title} is great!`);
}
Mapping Arrays with JSX
<ul>
{props.ingredients.map((ingredient, i) => (
<li key="{i}">{ingredient}</li>
))}
</ul>
Babel
template file to use
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>React Examples</title>
</head>
<body>
<div id="root"></div>
<!-- React Library & React DOM -->
<script src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// JSX code here. Or link to separate JavaScript file that contains JSX.
</script>
</body>
</html>
Recipes as JSX
const data = [
{
name: "Baked Salmon",
ingredients: [
{ name: "Salmon", amount: 1, measurement: "l lb" },
{ name: "Pine Nuts", amount: 1, measurement: "cup" },
{ name: "Butter Lettuce", amount: 2, measurement: "cups" },
{ name: "Yellow Squash", amount: 1, measurement: "med" },
{ name: "Olive Oil", amount: 0.5, measurement: "cup" },
{ name: "Garlic", amount: 3, measurement: "cloves" }
],
steps: [
"Preheat the oven to 350 degrees.",
"Spread the olive oil around a glass baking dish.",
"Add the yellow squash and place in the oven for 30 mins.",
"Add the salmon, garlic, and pine nuts to the dish.",
"Bake for 15 minutes.",
"Remove from oven. Add the lettuce and serve."
]
},
{
name: "Fish Tacos",
ingredients: [
{ name: "Whitefish", amount: 1, measurement: "l lb" },
{ name: "Cheese", amount: 1, measurement: "cup" },
{ name: "Iceberg Lettuce", amount: 2, measurement: "cups" },
{ name: "Tomatoes", amount: 2, measurement: "large" },
{ name: "Tortillas", amount: 3, measurement: "med" }
],
steps: [
"Cook the fish on the grill until cooked through.",
"Place the fish on the 3 tortillas.",
"Top them with lettuce, tomatoes, and cheese."
]
}
];
// A function component for an individual Recipe
function Recipe (props) {
...
}
// A function component for the Menu of Recipes
function Menu (props) {
return (
<article>
<header>
<h1>{props.title}</h1>
</header>
<div className="recipes">
{props.recipes.map((recipe, i) => (
<Recipe
key={i}
name={recipe.name}
ingredients={recipe.ingredients}
steps={recipe.steps}
/>
))}
// equivalent to
{
props.recipes.map((recipe, i) => <Recipe key={i} {...recipe} />);
}
</div>
</article>
);
}
// A call to ReactDOM.render to render our Menu into the current DOM
ReactDOM.render(
<Menu recipes={data} title="Delicious Recipes" />,
document.getElementById("root")
);
we can access title & recipes vars directly, so vars don't have to be prefixed with props:
function Menu({ title, recipes }) {
return (
<article>
<header>
<h1>{title}</h1>
</header>
<div className="recipes">
{recipes.map((recipe, i) => (
<Recipe key={i} {...recipe} />
))}
</div>
</article>
);
}
code for the component for each individual recipe:
function Recipe({ name, ingredients, steps }) {
return (
<section id={name.toLowerCase().replace(/ /g, "-")}>
<h1>{name}</h1>
<ul className="ingredients">
{ingredients.map((ingredient, i) => (
<li key={i}>{ingredient.name}</li>
))}
</ul>
<section className="instructions">
<h2>Cooking Instructions</h2>
{steps.map((step, i) => (
<p key={i}>{step}</p>
))}
</section>
</section>
);
}
React fragments
function Cat({ name }) {
return (
<React.Fragment>
<h1>The cat's name is {name}</h1>
<p>He's good.</p>
</React.Fragment>
);
}
// equivalent to
function Cat({ name }) {
return (
<>
<h1>The cat's name is {name}</h1>
<p>He's good.</p>
</>
);
}
Intro to webpack
- Code splitting
- Minification
- Feature flagging
- Hot module replacement (HMR)
- Modularity
- Composition
- Speed
- Consistency
Installing webpack
$ npm install --save-dev webpack@next webpack-cli
Installing Babel dependencies
$ npm install babel-loader @babel/core --save-dev
Specify Babel presets
$ npm install @babel/preset-env @babel/preset-react --save-dev
React State Management
Reference
- React Icons
- Personal blogs