Notes from Learning React, 2nd Edition by Porcello & Banks

Installation

// initialize a nodejs project (creates package.json)
$ npm init -y

// package manager 
$ npm install yarn

$ yarn install packagename

$ yarn remove packagename

JavaScript

Kangax compatibility table

Three ways to declare variables are

  1. const
  2. var
  3. 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

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

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

  1. at least one argument
  2. return a value or another function
  3. 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