From 2129429d1434a707d1f91c16c906b61003c58308 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Sun, 18 Jul 2021 15:27:29 -0400 Subject: [PATCH 01/13] WIP converting js-intro to typescript --- lessons/js-intro/arrays.md | 59 ++-- lessons/js-intro/assets/car-type-error.png | Bin 0 -> 53070 bytes .../assets/car-type-should-be-error.png | Bin 0 -> 19539 bytes lessons/js-intro/assets/car-type.png | Bin 0 -> 38633 bytes lessons/js-intro/assets/printit-error-2.png | Bin 0 -> 73630 bytes lessons/js-intro/assets/printit-error.png | Bin 0 -> 46410 bytes lessons/js-intro/assets/type-inference.png | Bin 0 -> 18038 bytes lessons/js-intro/control-flow.md | 30 +- lessons/js-intro/functions.md | 218 ++++++++++--- lessons/js-intro/loops.md | 56 ++-- lessons/js-intro/objects.md | 288 ++++++++---------- lessons/js-intro/operators.md | 52 ++-- lessons/js-intro/text-formatting.md | 30 +- lessons/js-intro/variables.md | 248 +++++++++++---- web/src/styles/markdown.css | 4 + 15 files changed, 630 insertions(+), 355 deletions(-) create mode 100644 lessons/js-intro/assets/car-type-error.png create mode 100644 lessons/js-intro/assets/car-type-should-be-error.png create mode 100644 lessons/js-intro/assets/car-type.png create mode 100644 lessons/js-intro/assets/printit-error-2.png create mode 100644 lessons/js-intro/assets/printit-error.png create mode 100644 lessons/js-intro/assets/type-inference.png diff --git a/lessons/js-intro/arrays.md b/lessons/js-intro/arrays.md index 66923ae6..15aff2e0 100644 --- a/lessons/js-intro/arrays.md +++ b/lessons/js-intro/arrays.md @@ -13,11 +13,17 @@ employee number one, `employee[2]` employee number two, and so on. The following statements create equivalent arrays: ```javascript -let array = new Array(element0, element1, ..., elementN); -let array = Array(element0, element1, ..., elementN); -let array = [element0, element1, ..., elementN]; +const array = new Array(element0, element1, ..., elementN); +const array = Array(element0, element1, ..., elementN); +const array = [element0, element1, ..., elementN]; ``` +The first two cases are challenging for TypeScript unless we define a type for +the array. For instance `const array = new Array('hello', 42)` will define an +array of `string` and convert the `42` to a string. However, +`const array = new Array(42, 'hello')` will be a TypeScript error. These are not +recommended approaches for creating arrays. + `element0, element1, ..., elementN` is a list of values for the array's elements. When these values are specified, the array is initialized with them as the array's elements. The array's length property is set to the number of @@ -26,15 +32,12 @@ arguments. The bracket syntax is called an "array literal" or "array initializer." It's shorter than other forms of array creation, and so is generally preferred. -To create an array with non-zero length, but without any items, either of the -following can be used: +To create an array with non-zero length, but without any items, the following +can be used. Note we need to give the `array` variable a type since we have no +initial values. ```javascript -let array = new Array(arrayLength) -let array = Array(arrayLength) - -// This has exactly the same effect -let array = [] +const array: number[] = [] array.length = arrayLength ``` @@ -43,17 +46,25 @@ array.length = arrayLength You can populate an array by assigning values to its elements. For example, ```javascript -let employees = [] +const employees: string[] = [] employees[0] = 'Casey Jones' employees[1] = 'Phil Lesh' employees[2] = 'August West' ``` -You can also populate an array when you create it: +You might be surprised that we can change the values of the entries of an array +that is defined `const employees: string[] = []`. This is because the `const` +refers to the variable name `employees`, **not** to the _values_ inside the +array. What `const` prevents here is a statement such as +`employees = ['Peter', 'Paul', 'Mary']` -```javascript -let myArray = new Array('Hello', myVar, 3.14159) -let myArray = ['Mango', 'Apple', 'Orange'] +You **can** have an array variable that you can neither assign a new value, +**or** change the contents of: + +```typescript +const cantChangeTheseValues: ReadonlyArray = [42, 100, 52] + +cantChangeTheseValues[0] = 1 ``` ## Referring to array elements @@ -62,7 +73,7 @@ You refer to an array's elements by using the element's ordinal number. For example, suppose you define the following array: ```javascript -let myArray = ['Wind', 'Rain', 'Fire'] +const myArray = ['Wind', 'Rain', 'Fire'] ``` You then refer to the first element of the array as `myArray[0]` and the second @@ -76,16 +87,19 @@ A common operation is to iterate over the values of an array, processing each one in some way. The simplest way to do this is as follows: ```javascript -let colors = ['red', 'green', 'blue'] +const colors = ['red', 'green', 'blue'] for (let index = 0; index < colors.length; index++) { console.log(colors[index]) } ``` +Note that the `index` variable is a `let` since we do need to reassign it +through the loop. + The `forEach()` method provides another way of iterating over an array: ```javascript -let colors = ['red', 'green', 'blue'] +const colors = ['red', 'green', 'blue'] colors.forEach(function (color) { console.log(color) }) @@ -95,7 +109,7 @@ Alternatively, You can shorten the code for the forEach parameter with Arrow Functions: ```javascript -let colors = ['red', 'green', 'blue'] +const colors = ['red', 'green', 'blue'] colors.forEach(color => console.log(color)) ``` @@ -104,10 +118,13 @@ colors.forEach(color => console.log(color)) > at SDG, we will prefer this way to iterate over arrays. Notice that we do not have an index. If we **do** want an index we can add that -as a second argument in our arrow function +as a second argument in our arrow function. + +Note that we do not need to apply a type to `color` or to `index`. TypeScript +will determine that they must be of types `string` and `number` respectively. ```javascript -let colors = ['red', 'green', 'blue'] +const colors = ['red', 'green', 'blue'] colors.forEach((color, index) => console.log(`The color at position ${index} is ${color}`) ) diff --git a/lessons/js-intro/assets/car-type-error.png b/lessons/js-intro/assets/car-type-error.png new file mode 100644 index 0000000000000000000000000000000000000000..d63ab29e5e9becde43a0379cf9fe8bee460f743d GIT binary patch literal 53070 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 KQZA(D_ul{obI!j2 literal 0 HcmV?d00001 diff --git a/lessons/js-intro/assets/car-type-should-be-error.png b/lessons/js-intro/assets/car-type-should-be-error.png new file mode 100644 index 0000000000000000000000000000000000000000..6b24ebe22a29ee4fe6c3e7c4fecf83e45f5bb0a5 GIT binary patch literal 19539 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 literal 0 HcmV?d00001 diff --git a/lessons/js-intro/assets/printit-error-2.png b/lessons/js-intro/assets/printit-error-2.png new file mode 100644 index 0000000000000000000000000000000000000000..845315b4f39dc30800308832297fe21537db5219 GIT binary patch literal 73630 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 UKEwi6Wno;}2tCb0^&1cV1;b~Ap8x;= literal 0 HcmV?d00001 diff --git a/lessons/js-intro/assets/printit-error.png b/lessons/js-intro/assets/printit-error.png new file mode 100644 index 0000000000000000000000000000000000000000..675d34edf7632ce4f6a75f09d532cf9b30d918be GIT binary patch literal 46410 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 Nyo|DRndI9K{}1@L%s~JE literal 0 HcmV?d00001 diff --git a/lessons/js-intro/assets/type-inference.png b/lessons/js-intro/assets/type-inference.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ef6888a730037d9cd3605bc2563a0af05043f6 GIT binary patch literal 18038 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 aV20L61L^&{-u?aa5}<;HeC2DC;Qt2@8)Uoy literal 0 HcmV?d00001 diff --git a/lessons/js-intro/control-flow.md b/lessons/js-intro/control-flow.md index 8ba5be3a..15a007fa 100644 --- a/lessons/js-intro/control-flow.md +++ b/lessons/js-intro/control-flow.md @@ -3,7 +3,7 @@ title: Control Flow order: 4 --- -JavaScript supports a compact set of statements, specifically control flow +TypeScript supports a compact set of statements, specifically control flow statements, that you can use to incorporate a great deal of interactivity in your application. This chapter provides an overview of these statements. @@ -14,21 +14,21 @@ Before we can discuss control flow, we need to understand the idea of a block. The most basic statement is a block statement that is used to group statements. The block is delimited by a pair of curly brackets: -```javascript +```typescript { - statement_1 - statement_2 - . - . - . - statement_n +statement_1 +statement_2 +. +. +. +statement_n } ``` ## Conditional Statements A conditional statement is a set of commands that executes if a specified -condition is true. JavaScript supports two conditional statements: `if...else` +condition is true. TypeScript supports two conditional statements: `if...else` and `switch`. **if statement** @@ -37,7 +37,7 @@ Use the if statement to execute a statement if a logical condition is `true`. Use the optional `else` clause to execute a statement if the condition is `false`. An if statement looks as follows: -```javascript +```typescript if (condition) { statement_1 } else { @@ -47,7 +47,7 @@ if (condition) { Here the condition can be any expression that evaluates to `true` or `false`. -> NOTE: In JavaScript all of these are considered `false`: `0`, `-0`, `null`, +> NOTE: In TypeScript all of these are considered `false`: `0`, `-0`, `null`, > `false`, `NaN`, `undefined`, and the empty string `""` If condition evaluates to `true`, `statement_1` is executed; otherwise, @@ -57,7 +57,7 @@ including further nested if statements. You may also compound the statements using `else if` to have multiple conditions tested in sequence, as follows: -```javascript +```typescript if (condition_1) { statement_1 } else if (condition_2) { @@ -74,7 +74,7 @@ evaluates to true will be executed. To execute multiple statements, group them within a block statement (`{ ... }`) . In general, it's good practice to always use block statements, especially when nesting if statements: -```javascript +```typescript if (condition) { statement_1_runs_if_condition_is_true statement_2_runs_if_condition_is_true @@ -90,7 +90,7 @@ A `switch` statement allows a program to evaluate an expression and attempt to match the expression's value to a case label. If a match is found, the program executes the associated statement. A switch statement looks as follows: -```javascript +```typescript switch (expression) { case label_1: statements_1 @@ -124,7 +124,7 @@ statement. When break is encountered, the program terminates switch and executes the statement following switch. If break were omitted, the statement for case "Cherries" would also be executed. -```javascript +```typescript switch (fruittype) { case 'Oranges': console.log('Oranges are $0.59 a pound.') diff --git a/lessons/js-intro/functions.md b/lessons/js-intro/functions.md index f43e615b..68759325 100644 --- a/lessons/js-intro/functions.md +++ b/lessons/js-intro/functions.md @@ -3,8 +3,8 @@ title: Functions order: 5 --- -Functions are one of the fundamental building blocks in JavaScript. A function -is a JavaScript procedure - a set of statements that performs a task or +Functions are one of the fundamental building blocks in TypeScript. A function +is a TypeScript procedure - a set of statements that performs a task or calculates a value. To use a function, you must define it somewhere in the scope from which you wish to call it. @@ -16,21 +16,66 @@ statement) consists of the `function` keyword, followed by: - The name of the function. - A list of parameters to the function, enclosed in parentheses and separated by commas. -- The JavaScript statements that define the function, enclosed in curly +- The TypeScript statements that define the function, enclosed in curly brackets, `{ }`. -For example, the following code defines a simple function named square: +For example, the following code defines a simple function named greet: -```javascript -function square(number) { - return number * number +```typescript +function greet() { + console.log('Hello there programmer!') } ``` -The function `square` takes one parameter, called `number`. The function -consists of one statement that says to return the parameter of the function -(that is, number) multiplied by itself. The statement `return` specifies the -value returned by the function: +The syntax is: + +```typescript +// function keyword +// | +// | name of the function +// | | +// | | required parenthesis where arguments will go +// | | | +// | | | opening scope of the function +// | | | | +// | | | | +// v v v v +function greet() { + console.log('Hello there programmer!') +} +``` + +We can also supply arguments to a function in case it needs information from the +outside world. For example, the following code defines a simple function named +square. + +```typescript +function square(valueToSquare: number) { + return valueToSquare * valueToSquare +} +``` + +Notice that we **must** supply a value to the `valueToSquare` argument, +otherwise TypeScript will _assume_ that the variable is of type `any` and we'll +lose any help the language will provide. + +Also notice that we have not declared the type of data the function **returns**. +This is because TypeScript can also **infer** the type from the `return` +statement. + +We can also define the return type on the function declaration here: + +```typescript +// argument type +// | +// | function return type +// | | +// | | +// v v +function square(valueToSquare: number): number { + return valueToSquare * valueToSquare +} +``` ## Calling functions @@ -39,13 +84,24 @@ function and specifies what to do when the function is called. Calling the function actually performs the specified actions with the indicated parameters. For example, if you define the function square, you could call it as follows: -```javascript +```typescript square(5) ``` The preceding statement calls the function with an argument of 5. The function executes its statements and returns the value 25. +What if we attempt to pass a value that is **not** a number, such as: + +```typescript +square('a circle') +``` + +In this case `TypeScript` will tell us this is an error. However, if we have not +configured our tools to not launch our site, or code, in the case of a +TypeScript error, the code will still **run**. Most of the tools we use will put +this error directly in our path so that we must resolve it. + ## Function expressions While the function declaration above is syntactically a statement, functions can @@ -53,11 +109,11 @@ also be created by a `function expression`. Such a function can be **anonymous**; it does not have to have a name. For example, the function square could have been defined as: -```javascript -let square = function (number) { - return number * number +```typescript +const square = function (valueToSquare: number) { + return valueToSquare * valueToSquare } -let x = square(4) // x gets the value 16 +const x = square(4) // x gets the value 16 ``` ## Functions are values of variables @@ -65,25 +121,56 @@ let x = square(4) // x gets the value 16 Notice in the example above that we can assign a function to a variable just like we assign a `number` or a `string` or any other kind of value. -In fact, in JavaScript, functions are values themselves and can be passed to +In fact, in TypeScript, functions are values themselves and can be passed to functions just like any other value. -```javascript -function printIt(array, func) { - for (let index = 0; index < array.length; index++) { - const value = array[index] +However, like any other argument in TypeScript we should supply a type! Since +our argument `func` will be a function that receives a number and returns a +number we define that type like: + +``` +(value: number) => number +``` + +The declaration uses the "arrow" style to define the type. + +So our function declaration is: + +```typescript +function printIt(numbers: number[], func: (value: number) => number) { +``` + +Sometimes the type declaration can become quite complex. In these cases we can +define a new type of our own and give it a name! + +```typescript +type PrintItFunction = (value: number) => number +``` + +and then use that in our declaration: + +```typescript +function printIt(numbers: number[], func: PrintItFunction) { +``` + +```typescript +type PrintItFunction = (value: number) => number + +function printIt(numbers: number[], func: PrintItFunction) { + for (let index = 0; index < numbers.length; index++) { + const value = numbers[index] const result = func(value) console.log(`After the function we turned ${value} into ${result}`) } } -const square = function (number) { - return number * number +function square(valueToSquare: number) { + return valueToSquare * valueToSquare } -const double = function (number) { - return number * 2 +function double(valueToDouble: number) { + return valueToDouble * 2 } const numbers = [1, 2, 3, 4, 5] @@ -101,12 +188,72 @@ printIt(numbers, double) // After the function we turned 5 into 10 ``` -> Passing functions as arguments to other functions is a very powerful pattern -> in JavaScript. We will be using this ability quite a bit in other lessons. +## Here is where TypeScript shines! + +What if we define a new function that doesn't fit the pattern our `printIt` +function expects. + +```typescript +function upperCase(stringToUpperCase: string) { + return stringToUpperCase.toUpperCase() +} + +const words = ['hello', 'there'] +printIt(words, upperCase) +``` + +The TypeScript system would immediately tell us that `words` isn't an array of +numbers and cannot be sent to `printIt`! -> Allowing functions to be treated as values for variables and to be passed as -> arguments is one of the things that makes JavaScript a **functional**-style -> language. +![](./assets/printit-error.png) + +If we "fix" this error by using our `numbers` variable, we'll see that +TypeScript then notifies us that the `upperCase` doesn't follow the style of the +`function` we are expecting! + +![](./assets/printit-error-2.png) + +## Advanced Topic + +TypeScript's ability to be flexible with types **would** allow us to handle +either case here. We do this by creating something called a "generic" type. + +The definition of `printIt` could become: + +```typescript +function printIt( + values: ValueType[], + func: (value: ValueType) => ValueType +) { + for (let index = 0; index < values.length; index++) { + const value = values[index] + const result = func(value) + + console.log(`After the function we turned ${value} into ${result}`) + } +} +``` + +In this case the `` after the `printIt` but before the arguments says +that `printIt` will work with a generic type of data and we will call that type +`ValueType`. Further on we say that `values` must be an array of whatever that +type is. Also, the argument `func` must also be a function that takes that type +and returns that type. As long as the `values` argument and the `func` agree on +the types, `printIt` will work just fine. + +[TypeScript generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) +are a powerful language feature and places a great deal of power and flexibility +in the hands of the developer. This is an advanced topic and not one that we +will use often in the course. + +## Functions as Arguments + +Passing functions as arguments to other functions is a very powerful pattern in +TypeScript. We will be using this ability quite a bit in other lessons. + +Allowing functions to be treated as values for variables and to be passed as +arguments is one of the things that makes TypeScript a **functional**-style +language. ## Scope @@ -120,7 +267,7 @@ function and any other variable to which the parent function has access. example: -```javascript +```typescript const PI = 3.14 const numbers = [1, 2, 4, 8, 16] @@ -153,18 +300,17 @@ and non-binding of this. In some functional patterns, shorter functions are welcome. Compare: -``` - -let elements = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ] +```typescript +let elements = ['Hydrogen', 'Helium', 'Lithium', 'Beryllium'] +// prettier-ignore let elementLengths1 = elements.map(function(element) { return element.length; }) console.log(elementLengths1) // logs [8, 6, 7, 9] let elementLengths2 = elements.map(element => element.length) -console.log(elementLengths2); // logs [8, 6, 7, 9] - +console.log(elementLengths2) // logs [8, 6, 7, 9] ``` The code for `elementLengths2` is shorter and has less code that we refer to as diff --git a/lessons/js-intro/loops.md b/lessons/js-intro/loops.md index 6d238cab..4827fbcd 100644 --- a/lessons/js-intro/loops.md +++ b/lessons/js-intro/loops.md @@ -8,10 +8,10 @@ loop as a computerized version of the game where you tell someone to take X steps in one direction then Y steps in another; for example, the idea "Go five steps to the east" could be expressed this way as a loop: -```javascript +```typescript for (let step = 0; step < 5; step++) { // Runs 5 times, with values of step 0 through 4. - console.log("Walking east one step"); + console.log('Walking east one step') } ``` @@ -21,7 +21,7 @@ that number could be zero). The various loop mechanisms offer different ways to determine the start and end points of the loop. There are various situations that are more easily served by one type of loop over the others. -The statements for loops provided in JavaScript are: +The statements for loops provided in TypeScript are: - `for statement` - `do...while statement` @@ -34,9 +34,9 @@ The statements for loops provided in JavaScript are: A for statement looks as follows: -```javascript +```typescript for ([initialExpression]; [condition]; [incrementExpression]) { - statement; + statement } ``` @@ -60,10 +60,10 @@ When a for loop executes, the following occurs: The do...while statement repeats until a specified condition evaluates to false. A do...while statement looks as follows: -```javascript +```typescript do { - statement; -} while (condition); + statement +} while (condition) ``` `statement` is always executed once before the condition is checked (and then @@ -78,9 +78,9 @@ execution stops and control passes to the statement following `do...while`. A `while` statement executes its statements as long as a specified `condition` evaluates to `true`. A while statement looks as follows: -```javascript +```typescript while (condition) { - statement; + statement } ``` @@ -97,24 +97,24 @@ statements. The following while loop iterates as long as n is less than three: -```javascript -let n = 0; -let x = 0; +```typescript +let n = 0 +let x = 0 while (n < 3) { - n++; - x += n; + n++ + x += n } ``` ## for...in statement The `for...in` statement iterates a specified variable over all the enumerable -properties of an object. For each distinct property, JavaScript executes the +properties of an object. For each distinct property, TypeScript executes the specified statements. A `for...in` statement looks as follows: -```javascript +```typescript for (variable in object) { - statements; + statements } ``` @@ -122,12 +122,12 @@ The following function takes as its argument an object and the object's name. It then iterates over all the object's properties and returns a string that lists the property names and their values. -```javascript -const car = { make: "Ford", model: "Mustang" }; +```typescript +const car = { make: 'Ford', model: 'Mustang' } -for (let property in car) { - const message = `The value of ${property} is ${car[property]}`; - console.log(message); +for (const property in car) { + const message = `The value of ${property} is ${car[property]}` + console.log(message) } // The value of make is Ford // The value of model is Mustang @@ -144,12 +144,10 @@ The `for...of` statement creates a loop iterating over iterable objects (including `Array`, `Map`, `Set`, arguments object and so on), invoking a block with statements to be executed for the value of each distinct property. -```javascript -var numbers = [3, 5, 7]; +```typescript +const numbers = [3, 5, 7] -for (let number of numbers) { - console.log(number); // logs 3, 5, 7 +for (const number of numbers) { + console.log(number) // logs 3, 5, 7 } ``` - ---- diff --git a/lessons/js-intro/objects.md b/lessons/js-intro/objects.md index 3040c75d..c223602c 100644 --- a/lessons/js-intro/objects.md +++ b/lessons/js-intro/objects.md @@ -5,73 +5,152 @@ order: 9 # Overview -Objects in JavaScript, just as in other languages like `C#`, can be compared to -objects in real life. The concept of objects in JavaScript can be understood +Objects in TypeScript, just as in other languages like `C#`, can be compared to +objects in real life. The concept of objects in TypeScript can be understood with real life, tangible objects. -In JavaScript, an object is a standalone entity, with properties and type. +In TypeScript, an object is a standalone entity, with properties and type. Compare it with a cup, for example. A cup is an object, with properties. A cup has a color, a design, weight, a material it is made of, etc. The same way, -JavaScript objects can have properties, which define their characteristics. +TypeScript objects can have properties, which define their characteristics. # Properties -A JavaScript object has properties associated with it. A property of an object +A TypeScript object has properties associated with it. A property of an object can be explained as a variable that is attached to the object. Object properties -are basically the same as ordinary JavaScript variables, except for the +are basically the same as ordinary TypeScript variables, except for the attachment to objects. The properties of an object define the characteristics of the object. You access the properties of an object with a simple dot-notation: -```javascript +```typescript objectName.propertyName ``` -Like all JavaScript variables, both the object name (which could be a normal +Like all TypeScript variables, both the object name (which could be a normal variable) and property name are case sensitive. You can define a property by assigning it a value. For example, let's create an object named myCar and give it properties named make, model, and year as follows: -```javascript -let myCar = new Object() +```typescript +// This code will have errors in TypeScript +const myCar = new Object() myCar.make = 'Ford' myCar.model = 'Mustang' myCar.year = 1969 ``` -The above example could also be written using an object initializer, which is a -comma-delimited list of zero or more pairs of property names and associated +In TypeScript however, we will receive errors that `make`, `model`, and `year` +are not properties of `Object` since the default `Object` type does not know +about them. + +A better way to write the above example is with an object initializer, which is +a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}): -```javascript -let myCar = { +```typescript +const myCar = { make: 'Ford', model: 'Mustang', year: 1969, } ``` -> Unassigned properties of an object are `undefined` (and not `null`). +TypeScript will see that the `myCar` variable is an object with two string +properties named `make` and `model`, and one number property named `year`. + +![](./assets/car-type.png) + +We could continue to make new cars and as long as we kept the same initial +properties they would all be the same **type** -```javascript -myCar.color // undefined +```typescript +const theirCar = { + make: 'Jeep', + model: 'Wrangler', + year: 2021, +} ``` +However, this object would **not** be the same type. + +```typescript +const otherCar = { + make: 'Honda', + modal: 'Fit', + year: 2020, +} +``` + +Do you see the problem? The property name for `model` has a **typo**! + +# Using types to find issues in our code + +Having defined types is a great way to ensure that we do not make these kinds of +errors in our code. A small typo like this could take us hours of time to find! +Imagine if our code base was tens of thousands of lines. It might be even longer +to find the issue show up. + +## Define a `type` + +We can teach TypeScript about a new specific type and give it a name of our +choice. + +```typescript +type Car = { + make: string + model: string + year: number +} +``` + +Now that we have our type: + +```typescript +type Car = { + make: string + model: string + year: number +} + +const myCar: Car = { + make: 'Ford', + model: 'Mustang', + year: 1969, +} + +const theirCar: Car = { + make: 'Jeep', + model: 'Wrangler', + year: 2021, +} + +const otherCar: Car = { + make: 'Honda', + modal: 'Fit', + year: 2020, +} +``` + +With this definition we can clearly see our error: + +![](./assets/car-type-error.png) + # Accessing properties using bracket notation (and by string) -Properties of JavaScript objects can also be accessed or set using a bracket +Properties of TypeScript objects can also be accessed or set using a bracket notation. So, for example, you could access the properties of the `myCar` object as follows: -```javascript +```typescript myCar['make'] = 'Ford' myCar['model'] = 'Mustang' myCar['year'] = 1969 ``` -An object property name can be any valid JavaScript string, or anything that can +An object property name can be any valid TypeScript string, or anything that can be converted to a string, including the empty string. -**However, any property name that is not a valid JavaScript identifier (for +**However, any property name that is not a valid TypeScript identifier (for example, a property name that has a space or a hyphen, or that starts with a number) can only be accessed using the square bracket notation.** @@ -79,50 +158,20 @@ This notation is also very useful when property names are to be dynamically determined (when the property name is not determined until runtime). Examples are as follows: -An object property name can be any valid JavaScript string, or anything that can +An object property name can be any valid TypeScript string, or anything that can be converted to a string, including the empty string. However, any property name -that is not a valid JavaScript identifier (for example, a property name that has +that is not a valid TypeScript identifier (for example, a property name that has a space or a hyphen, or that starts with a number) can only be accessed using the square bracket notation. This notation is also very useful when property names are to be dynamically determined (when the property name is not determined -until runtime). Examples are as follows: - -```javascript -let anObject = new Object() -let someString = 'stringProperty' -let randomNumber = Math.random() -let anotherObject = new Object() - -anObject.type = 'Dot syntax' -anObject['date created'] = 'String with space' -anObject[someString] = 'String value' -anObject[randomNumber] = 'Random Number' -anObject[anotherObject] = 'Object' -anObject[''] = 'Even an empty string' - -console.log(anObject) - -// Output looks like: -// { -// type: 'Dot syntax', -// 'date created': 'String with space', -// stringProperty: 'String value', -// '0.6854535624185345': 'Random Number', -// '[object Object]': 'Object', -// '': 'Even an empty string' -// } -``` +until runtime). -You can also access properties by using a string value that is stored in a -variable: +## Warning about the `[]` notation in TypeScript -```javascript -let propertyName = 'make' -myCar[propertyName] = 'Ford' +TypeScript does not check our object type when using this operator, so be +cautious when using it. -propertyName = 'model' -myCar[propertyName] = 'Mustang' -``` +![](./assets/car-type-should-be-error.png) # Using a variable to indicate a property name @@ -133,10 +182,10 @@ new object and use the variable to indicate what property to set. We could do something like this: -```javascript -let propertyName = 'model' +```typescript +const propertyName = 'model' -let myOtherCar = { +const myOtherCar = { [propertyName] = 'Mustang' } ``` @@ -150,8 +199,8 @@ below we get a fairly powerful pair of features we'll be using later. Let's take our car example again: -```javascript -let myCar = { +```typescript +const myCar = { make: 'Ford', model: 'Mustang', year: 1969, @@ -161,8 +210,8 @@ let myCar = { If we wanted to make another instance of this object, but with a different `year`, we could do something like this: -```javascript -let myOtherCar = { +```typescript +const myOtherCar = { make: myCar.make, model: myCar.model, year: 1971, @@ -171,14 +220,14 @@ let myOtherCar = { And now we would have a new object, independent of the first, with a different year. However, you could imagine that if we had many properties it would be -cumbersome to repeat all the keys and values. Fortunately, recent JavaScript +cumbersome to repeat all the keys and values. Fortunately, recent TypeScript allows for a shortcut to "expand" all the keys and values. This is known as the `spread` operator and is noted as `...` So let's write this again using the spread operator: -```javascript -let myOtherCar = { +```typescript +const myOtherCar = { ...myCar, year: 1971, } @@ -186,17 +235,17 @@ let myOtherCar = { Much better, we've saved one line of code, however, it now doesn't matter how many property/value paris `myCar` has since they are all now part of -`myOtherCar` since this is effectively what JavaScript is doing for us: +`myOtherCar` since this is effectively what TypeScript is doing for us: -```javascript -let myOtherCar = { +```typescript +const myOtherCar = { ...myCar, year: 1971, } // is the same as: -let myOtherCar = { +const myOtherCar = { make: myCar.make, model: myCar.model, year: myCar.year, @@ -214,8 +263,8 @@ comes **after** the spread operator, our value of `1971` overrides the one from If we wrote the code like this: -```javascript -let myOtherCar = { +```typescript +const myOtherCar = { year: 1971, ...myCar, @@ -223,7 +272,7 @@ let myOtherCar = { // is the same as: -let myOtherCar = { +const myOtherCar = { year: 1971, make: myCar.make, @@ -235,95 +284,6 @@ let myOtherCar = { In this case, our property/value pair of `year: 1971` would be lost since the `...` spread would reintroduce the value from `myCar` -# Spread operator and bracket operator together. - -Let's define a function called `copyCar` which will make a copy of an object and -allow us to change a specific property to a supplied value. - -The function will look like this: - -```javascript -function copyCar(existingCar, propertyName, propertyValue) { - // Code here -} -``` - -We'd like to be able to call it like this: - -```javascript -let myCar = { - make: 'Ford', - model: 'Mustang', - year: 1969, -} - -let myNewerCar = copyCar(myCar, 'year', 2014) -let myOtherCar = copyCar(myCar, 'model', 'Bronco') -``` - -In order to have this code work let's implement `copyCar`. We'll start by using -the `...` operator to make our new car. - -```javascript -function copyCar(existingCar, propertyName, propertyValue) { - let newCarObject = { - ...existingCar, - } - - return newCarObject -} -``` - -However, that is not enough, we need to set the property given by `propertyName` -to the value given by `propertyValue`. We could do this: - -```javascript -function copyCar(existingCar, propertyName, propertyValue) { - let newCarObject = { - ...existingCar, - } - - newCarObject[propertyName] = propertyValue - - return newCarObject -} -``` - -However, using the same `[]` bracket syntax we can specify the value directly -when we create the object: - -```javascript -function copyCar(existingCar, propertyName, propertyValue) { - let newCarObject = { - ...existingCar, - [propertyName]: propertyValue, - } - - return newCarObject -} -``` - -And now we do not need the extra line of code in our method. The `[]:` style -syntax allows us to put a variable name inside the `[]` to indicate what -property we want to change and then specify the value on the right hand side. We -could have called our variable anything, but `propertyName` was a nice name, we -could have called this: `name`, `thingToChange`, `tacoTuesday` or anything else. -Since JavaScript is taking the **value** of that variable to determine which -property to change. We are supplying this when we call the method (e.g. `make`, -`year`, etc.) - -To refactor this code a little more, we can remove the need to create the -temporary variable just to return it one line later: - -```javascript -function copyCar(existingCar, propertyName, propertyValue) { - return { - ...existingCar, - [propertyName]: propertyValue, - } -} -``` - # Resources For more details on objects, see diff --git a/lessons/js-intro/operators.md b/lessons/js-intro/operators.md index 193abfe6..6dfbbbcb 100644 --- a/lessons/js-intro/operators.md +++ b/lessons/js-intro/operators.md @@ -5,14 +5,14 @@ order: 6 import AdvancedTopic from '@handbook/AdvancedTopic' -This section describes JavaScript's expressions and operators, including +This section describes TypeScript's expressions and operators, including assignment, comparison, arithmetic, bitwise, logical, string, ternary and more. A complete and detailed list of operators and expressions is also available in the -[ MDN reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators) +[ MDN reference](https://developer.mozilla.org/en-US/docs/Web/TypeScript/Reference/Operators) -JavaScript has the following types of operators. This section describes the +TypeScript has the following types of operators. This section describes the operators and contains information about operator precedence. - Assignment operators @@ -37,7 +37,7 @@ There are also compound assignment operators that are shorthand for the operations listed in the following table: | Name | Shorthand operator | Meaning | -| ------------------------------- | ------------------ | ------------- | +| ------------------------------- | ------------------ | ------------- | ------ | --- | | Assignment | `x = y` | `x = y` | | Addition assignment | `x += y` | `x = x + y` | | Subtraction assignment | `x -= y` | `x = x - y` | @@ -50,26 +50,26 @@ operations listed in the following table: | Unsigned right shift assignment | `x >>>= y` | `x = x >>> y` | | Bitwise AND assignment | `x &= y` | `x = x & y` | | Bitwise XOR assignment | `x ^= y` | `x = x ^ y` | -| Bitwise OR assignment | `x |= y` | `x = x | y` | +| Bitwise OR assignment | `x | = y` | `x = x | y` | | | | | **Destructuring Assignment** For more complex assignments, the destructuring assignment syntax is a -JavaScript expression that makes it possible to extract data from arrays or +TypeScript expression that makes it possible to extract data from arrays or objects using a syntax that mirrors the construction of array and object literals. -```javascript -let numbers = ['one', 'two', 'three'] +```typescript +const numbers = ['one', 'two', 'three'] // without destructuring -let one = numbers[0] -let two = numbers[1] -let three = numbers[2] +const one = numbers[0] +const two = numbers[1] +const three = numbers[2] // with destructuring -let [one, two, three] = numbers +const [one, two, three] = numbers ``` ## Comparison Operators @@ -77,7 +77,7 @@ let [one, two, three] = numbers A comparison operator compares its operands and returns a logical value based on whether the comparison is true. The operands can be numerical, string, logical, or object values. Strings are compared based on standard ordering. In most -cases, if the two operands are not of the same type, JavaScript attempts to +cases, if the two operands are not of the same type, TypeScript attempts to convert them to an appropriate type for the comparison. This behavior generally results in comparing the operands numerically. The sole exceptions to type conversion within comparisons involve the === and !== operators, which perform @@ -85,9 +85,9 @@ strict equality and inequality comparisons. These operators do not attempt to convert the operands to compatible types before checking equality. The following table describes the comparison operators in terms of this sample code: -```javascript -let var1 = 3 -let var2 = 4 +```typescript +const var1 = 3 +const var2 = 4 ``` | Operator | Description | Examples returning true | @@ -111,12 +111,12 @@ operators are addition (+), subtraction (-), multiplication (\*), and division used with floating point numbers (in particular, note that division by zero produces Infinity). For example: -```javascript +```typescript 1 / 2 // 0.5 1 / 2 == 1.0 / 2.0 // this is true ``` -In addition to the standard arithmetic operations (+, -, \* /), JavaScript +In addition to the standard arithmetic operations (+, -, \* /), TypeScript provides the arithmetic operators listed in the following table: | Operator | Description | Example | @@ -138,14 +138,14 @@ A bitwise operator treats their operands as a set of 32 bits (zeros and ones), rather than as decimal, hexadecimal, or octal numbers. For example, the decimal number nine has a binary representation of `1001`. Bitwise operators perform their operations on such binary representations, but they return standard -JavaScript numerical values. +TypeScript numerical values. -The following table summarizes JavaScript's bitwise operators. +The following table summarizes TypeScript's bitwise operators. | Operator | Usage | Description | -| ------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------ | +| ------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------ | --- | ------------------------------------------------------------------------------------------------ | | Bitwise AND | `a & b` | Returns a one in each bit position for which the corresponding bits of both operands are ones. | -| Bitwise OR | `a | b` | | Returns a zero in each bit position for which the corresponding bits of both operands are zeros. | +| Bitwise OR | `a | b` | | Returns a zero in each bit position for which the corresponding bits of both operands are zeros. | | Bitwise XOR | `a ^ b` | Returns a zero in each bit position for which the corresponding bits are the same. | | [Returns a one in each bit position for which the corresponding bits are different.] | | Bitwise NOT | `~ a` | Inverts the bits of its operand. | @@ -156,9 +156,9 @@ The following table summarizes JavaScript's bitwise operators. Bitwise operator examples | Expression | Result | Binary Description | -| ---------- | ------ | -------------------------------------------- | +| ---------- | ------ | -------------------------------------------- | ----- | ------------ | | `15 & 9` | `9` | `1111 & 1001 = 1001` | -| `15 | 9` | `15` | `1111 | 1001 = 1111` | +| `15 | 9` | `15` | `1111 | 1001 = 1111` | | `15 ^ 9` | `6` | `1111 ^ 1001 = 0110` | | `~15` | `-16` | `~00000000...00001111 = 11111111...11110000` | | `~9` | `-10` | `~00000000...00001001 = 11111111...11110110` | @@ -183,9 +183,9 @@ used with non-Boolean values, they may return a non-Boolean value. The logical operators are described in the following table. | Operator | Usage | Description | -| -------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| -------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ------------------------------------------------------------------------- | | Logical AND
&&
|
expr1 && expr2
| Returns `expr1` if it can be converted to `false`; otherwise, returns `expr2`. Thus, when used with Boolean values, `&&` returns `true` if both operands are `true`; otherwise, returns `false`. | -| Logical OR
\|\|
|
expr1 \|\| expr2
| Returns expr1 if it can be converted to true; otherwise, returns expr2. Thus, when used with Boolean values, | | returns true if either operand is true; if both are false, returns false. | +| Logical OR
\|\|
|
expr1 \|\| expr2
| Returns expr1 if it can be converted to true; otherwise, returns expr2. Thus, when used with Boolean values, | | returns true if either operand is true; if both are false, returns false. | | Logical NOT
!
|
!expr
| Returns false if its single operand that can be converted to true; otherwise, returns true. | --- diff --git a/lessons/js-intro/text-formatting.md b/lessons/js-intro/text-formatting.md index c3a26fd2..34f2f760 100644 --- a/lessons/js-intro/text-formatting.md +++ b/lessons/js-intro/text-formatting.md @@ -3,7 +3,7 @@ title: Strings order: 7 --- -JavaScript's String type is used to represent textual data. It is a set of +TypeScript's String type is used to represent textual data. It is a set of "elements." Each element in the String occupies a position in the String. The first element is at index 0, the next at index 1, and so on. The length of a `String` is the number of elements in it. You can create strings using string @@ -13,28 +13,36 @@ literals or string objects. You can create simple strings using either single or double quotes: -```javascript -"foo"; -"bar"; ``` +'foo' +"bar" +``` + +## Commonly used methods of strings + +| Method | Description | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `indexOf`, `lastIndexOf` | Return the position of specified substring in the string or last position of specified substring, respectively. | +| `startsWith`, `endsWith`, `includes` | Returns whether or not the string starts, ends or contains a specified string. | +| `concat` | Combines the text of two strings and returns a new string. | +| `split` | Splits a String object into an array of strings by separating the string into substrings. | +| `slice` | Extracts a section of a string and returns a new string. | +| `substring`, `substr` | Return the specified subset of the string, either by specifying the start and end indexes or the start index and a length. | +| `match`, `matchAll`, `replace`, `search` | Work with regular expressions. | +| `toLowerCase`, `toUpperCase` | Return the string in all lowercase or all uppercase, respectively. | +| | | -## Methods of strings +## Less commonly used methods of strings | Method | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `charAt`, `charCodeAt`, `codePointAt` | Return the character or character code at the specified position in string. | -| `indexOf`, `lastIndexOf` | Return the position of specified substring in the string or last position of specified substring, respectively. | -| `startsWith`, `endsWith`, `includes` | Returns whether or not the string starts, ends or contains a specified string. | -| `concat` | Combines the text of two strings and returns a new string. | | `fromCharCode`, `fromCodePoint` | Constructs a string from the specified sequence of Unicode values. This is a method of the String class, not a String instance. | | `split` | Splits a String object into an array of strings by separating the string into substrings. | | `slice` | Extracts a section of a string and returns a new string. | | `substring`, `substr` | Return the specified subset of the string, either by specifying the start and end indexes or the start index and a length. | | `match`, `matchAll`, `replace`, `search` | Work with regular expressions. | | `toLowerCase`, `toUpperCase` | Return the string in all lowercase or all uppercase, respectively. | -| `normalize` | Returns the Unicode Normalization Form of the calling string value. | | `repeat` | Returns a string consisting of the elements of the object repeated the given times. | | `trim` | Trims whitespace from the beginning and end of the string. | | | | - ---- diff --git a/lessons/js-intro/variables.md b/lessons/js-intro/variables.md index 2841e1c9..0842413e 100644 --- a/lessons/js-intro/variables.md +++ b/lessons/js-intro/variables.md @@ -3,19 +3,21 @@ title: Variables order: 3 --- -Like in most modern programming languages, in JavaScript we store information in +Like in most modern programming languages, in TypeScript we store information in `variables.` Variables are placeholders with meaningful names that we use to store a value so that we can refer to it later. +# Variable Naming Rules + The names of variables, called identifiers, conform to certain rules. -A JavaScript identifier must start with a letter, underscore (`_`), or dollar -sign (`$`); subsequent characters can also be digits (`0-9`). Because JavaScript +A TypeScript identifier must start with a letter, underscore (`_`), or dollar +sign (`$`); subsequent characters can also be digits (`0-9`). Because TypeScript is case sensitive, letters include the characters "`A`" through "`Z`" (uppercase) and the characters "`a`" through "`z`" (lowercase). Some examples of legal names are `Number_hits`, `temp99`, `$credit`, and `_name`. -There are four ways we can assign a variable in JavaScript +There are four ways we can assign a variable in TypeScript | | | | ----------------- | ------------------------------------------------------------------------------- | @@ -27,16 +29,16 @@ There are four ways we can assign a variable in JavaScript `undeclared global` variables are highly discouraged as they can often lead to unexpected behavior. In our coding we will always use `var`, `let`, or `const.` -In fact, in modern JavaScript we will restrict our usage to `let` and `const.` +In fact, in modern TypeScript we will restrict our usage to `let` and `const.` `const` variables are assigned a value on the same statement where they are declared. They can also not be re-assigned at a later date. -> The variables that you create should be defaulted to using `const`. This will -> help you keep your variables, values and data more organized and reliable. +> You should default to using const when creating variables. This will help you +> keep your variables, values and data more organized and reliable. -```javascript -const answer = 42; +```typescript +const answer = 42 ``` The value of this variable cannot be changed. @@ -46,42 +48,154 @@ changed. The difference between these two ways of declaring a variable have to do with `scope.` We haven't discussed `scope` yet, so for now we will limit ourselves to using the `let` style of declaring variables. -```javascript -let score = 98; +```typescript +let score = 98 ``` The value of the `score` variable can be changed at a later time. That is, we can increment it, decrement it, or change it to any other value we like. -**Variables without assigned values** +# Wait, what about `types` in `TypeScript` + +We are programming in a language named `TypeScript` so shouldn't we be declaring +variable types? + +Luckily for us, TypeScript, like C#, has +[type inference](https://www.typescriptlang.org/docs/handbook/type-inference.html). +This means that if we assign a value for a variable when we declare it, +TypeScript will make an inference, or guess, at the type for the variable. + +So the following code declares variables that are of type `number`! + +```typescript +const answer = 42 +let score = 98 +``` + +If you define these variables in a `.ts` file in Visual Studio Code, you can +hover over the variable and see the inferred type. + +![](./assets/type-inference.png) + +TypeScript can infer quite a bit about a variable if we give it a good default +value. + +```typescript +const name = 'Mary' +const students = ['Mary', 'Steven', 'Paulo', 'Sophia'] +const scores = [98, 100, 55, 100] +``` + +The variable `name` will have a type of `string` while `students` will have the +type `string[]` and `scores` the type `number[]`. + +The type `string[]` indicates that the variable is an array with every element +being a `string`. Similarly, `number[]` indicates the variable is an array with +every element being a `number`. + +What about an array that has different types of elements? Unlike `C#`, +TypeScript can handle that just fine, and in a very nice way. + +```typescript +const differentKindsOfThings = [42, 'Ice Cream', 100, 'Tacos'] +``` + +The variable `differentKindsOfThings` will have the type `(string | number)[]`. +The `|` is a `union` of types. This means that `differentKindsOfThings` is an +array of elements that can be **either** a `string` or a `number`. + +# Declaring types specifically + +Type inference is powerful and frees us from having to type additional syntax. +However, we cannot use type inference everywhere in our code. For instance, when +declaring functions we'll need to provide types for our arguments. + +Also, some developers do not prefer to rely on their editor's features to show +them the type of a variable and prefer to be `explicit` instead of `implict` +with typing. + +To declare the variables again we can use a syntax that includes the variable +types explicitly. + +```typescript +const name: string = 'Mary' + +const students: string[] = ['Mary', 'Steven', 'Paulo', 'Sophia'] + +const scores: number[] = [98, 100, 55, 100] + +const differentKindsOfThings: (string | number)[] = [ + 42, + 'Ice Cream', + 100, + 'Tacos', +] +``` + +This syntax isn't significantly different as it only uses `: TYPE` after the +variable name declaration. We'll use type inference most of the time when +writing code in the handbook and our projects. + +Once we introduce the idea of TypeScript objects we'll discuss why specifying an +explict type is useful. + +# Without assigned values When declaring a variable with `let` we do not have to specify a value. After declaring a variable but before assigning it a value, the variable will contain -a special value known as `undefined` +a special value known as `undefined`. + +The `undefined` value is different than our experience with `null`. While `null` +implies that the variable has **no value**, `undefined` implies that we have +never defined a value at all. Generally, `undefined` should be avoided in our +code as it leads to bugs. + +Also since we did not provide a type for this variable, TypeScript will assign +the special type `any`. This variable will now accept **any** type of value. The +`any` type can lead to bugs in our software that would be avoided if we applied +a type. + +## `undefined` and `any` should be avoided in our code + +Since we are just starting with learning TypeScript we'll focus on a code style +that prevents the need for the `undefined` value and the `any` type. + +In fact, we can turn on code checking tools to make sure we avoid them! + +## Bad form + +```typescript +let name +// name contains 'undefined' and is of type `any` + +name = 'Jane' +// name now contains the value 'Jane', but `name` is still an `any` type. +``` + +## Better form -```javascript -let name; -// name contains 'undefined' +```typescript +let name: string +// name contains 'undefined' and should be of type `string` -name = "Jane"; +name = 'Jane' // name now contains the value 'Jane' ``` -Most of the time we are able to declare a variable and assign a value at the -same time. However, it is sometimes useful to declare the variable and assign -it's value later. Once we introduce conditions and functions we will see cases -of this. +## Best form + +```typescript +const name = 'Jane' // name contains the value 'Jane' and we avoid any issue with `undefined` +``` -# Types +# More Types -# Basic Types +## Basic Types As you saw when declaring variables, there are different types of values in -JavaScript. You may have also noticed that we did not have to tell a variable -what kind of value we were assigning. The same syntax is used regardless of the -variable type. +TypeScript. -Here are our first few types that are in JavaScript, we will build on this later +Here are our first few types that are in TypeScript, we will build on this later | | | | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | @@ -97,44 +211,65 @@ redundant. Here is an example of the difference between the two: ## Data Type Conversion -JavaScript is a dynamically typed language. That means you don't have to specify +TypeScript is a dynamically typed language. That means you don't have to specify the data type of a variable when you declare it, and data types are converted automatically as needed during script execution. So, for example, you could define a variable as follows: -```javascript -let answer = 42; +```typescript +let answer = 42 ``` -And later, you could assign the same variable a string value, for example: +And later, you could try to assign the same variable a string value, for +example: -```javascript -answer = "Thanks for all the fish..."; +```typescript +answer = 'Thanks for all the fish...' ``` -Because JavaScript is dynamically typed, this assignment does not cause an error -message. +
+The following is a key point about the relationship between TypeScript and JavaScript +
+ +Here is where `TypeScript`'s relationship to `JavaScript` shows. While our +`TypeScript` system will notify us this is an _error_, the code will still +execute! + +Because TypeScript is +[transpiled](https://en.wikipedia.org/wiki/Source-to-source_compiler) to the +JavaScript language our browsers (and other tools) know how to execute, it will +not _prevent_ this code from executing when we reach the JavaScript execution +stage. + +
+
+
In expressions involving numeric and string values with the + operator, -JavaScript converts numeric values to strings. For example, consider the +TypeScript converts numeric values to strings. For example, consider the following statements: -```javascript -let x = "The answer is " + 42; // "The answer is 42" -let y = 42 + " is the answer"; // "42 is the answer" +```typescript +let x = 'The answer is ' + 42 // "The answer is 42" +let y = 42 + ' is the answer' // "42 is the answer" ``` -In statements involving other operators, JavaScript does not convert numeric +In statements involving other operators, TypeScript does not convert numeric values to strings. For example: -```javascript -"37" - 7; // 30 -"37" + 7; // "377" +```typescript +let x: string + +x = '37' - 7 // 30 and notes that this is an error, assigning a number to a string +``` + +```typescript +x = '37' + 7 // "377" and no error since we are converting the `7` to a string first. ``` ## Literals -You use literals to represent values in JavaScript. These are fixed values, not +You use literals to represent values in TypeScript. These are fixed values, not variables, that you _literally_ provide in your script. This section describes the following types of literals: @@ -191,21 +326,23 @@ quotation marks. A string must be delimited by quotation marks of the same type; that is, either both single quotation marks or both double quotation marks. The following are examples of string literals: -```javascript -"foo"; -"bar"; -"1234"; -"one line \n another line"; -"John's cat"; +```typescript +'foo' +'bar' +'1234' +'one line \n another line' +"John's cat" ``` Template literals are also available. Template literals are enclosed by the back-tick (\`) (grave accent) character instead of double or single quotes. Inside the backticks we can use `${}` to evaluate statements -```javascript -let score = 56; -let prompt = `The current score is ${score} and the next score is ${score + 1}`; +```typescript +const score = 56 +const prompt = `The current score is ${score} and the next score is ${ + score + 1 +}` // The current score is 56 and the next score is 57 ``` @@ -213,6 +350,11 @@ let prompt = `The current score is ${score} and the next score is ${score + 1}`; The Boolean type has two literal values: `true` and `false`. +```typescript +const typeScriptIsAwesome = true +const learningTypeScriptIsHard = false +``` + --- Now we have data, we need ways to manipulate the variables diff --git a/web/src/styles/markdown.css b/web/src/styles/markdown.css index b4322b06..7dd4c1ce 100644 --- a/web/src/styles/markdown.css +++ b/web/src/styles/markdown.css @@ -8,6 +8,10 @@ @apply mt-0 mb-4; } +.markdown > p { + @apply mt-0 mb-4; +} + .markdown li + li { @apply mt-1; } From b7b75fa4d4ef46526e4a2fc7a75f7c20e613a77a Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Sun, 18 Jul 2021 16:08:49 -0400 Subject: [PATCH 02/13] Updates js-intro lecture to typescript --- lessons/js-intro/lecture.md | 641 +++++++++++++++++++++++------------- lessons/js-intro/objects.md | 6 +- 2 files changed, 417 insertions(+), 230 deletions(-) diff --git a/lessons/js-intro/lecture.md b/lessons/js-intro/lecture.md index 45d6ec49..48e2f243 100644 --- a/lessons/js-intro/lecture.md +++ b/lessons/js-intro/lecture.md @@ -1,42 +1,10 @@ theme: Next, 1 -# [fit] JavaScript - ---- - -# [fit] What is it? - ---- - -# Purpose - -- Initially to make web pages "interactive". -- Is the lingua franca of the front-end web, the default programming language. -- Has extended to the server-side via implementations like Node.js -- Showing up in more places like embedded environments. - ---- - -# History - -- Created around 1995. -- JavaScript is not Java. -- Became a standard in 1997. - ---- - -# Lineage - -- Draws from Java, C, Scheme. -- Some syntax will look familiar since Java and C also inspire C#. - ---- - # Also statement based -Like C# a JavaScript program is composed of a sequence of statements. +Like C# a TypeScript program is composed of a sequence of statements. -Like a C# program, these statements may end with a semicolon, `;`. However, these are _optional_ in JavaScript, and we will code without them. +Like a C# program, these statements may end with a semicolon, `;`. However, these are _optional_ in TypeScript, and we will code without them. Comments are the same as in C#, single lines with `//` and multiple lines with `/*` and `*/`. @@ -46,7 +14,7 @@ Comments are the same as in C#, single lines with `//` and multiple lines with ` [.autoscale: true] -JavaScript also has `types`, some of which will be familiar. +TypeScript also has `types`, some of which will be familiar. - `number` - `string` @@ -58,15 +26,9 @@ JavaScript also has `types`, some of which will be familiar. --- -# Dynamically typed - -Variables are not bound to their value type. - -Unlike `C#`, once assigned a type, like a `number` the variable can change to accept a `string`, or a `boolean` - -JavaScript can also automatically convert values from one type to another. +# Can create our own types! -It is also more loose about how it _compares_ values. +During this lecture, and in our work, we'll create our own types! --- @@ -122,13 +84,13 @@ When looking at code on the web, e.g., StackOverflow, and blog posts, you will s They can also not be re-assigned later in the code. -```javascript +```typescript const answer = 42 ``` Not allowed, will be an error: -```javascript +```typescript answer = answer + 1 ``` @@ -140,11 +102,11 @@ answer = answer + 1 They **can** be re-assigned later in the code. -```javascript +```typescript let score = 98 ``` -```javascript +```typescript score = score + 1 ``` @@ -161,60 +123,158 @@ That is, they are valid and accessible inside the current block. A block is: --- +# So, what about types? + +- Shouldn't we be declaring types for our variables? +- Like `C#`, TypeScript has type inference. + +So the following code declares variables that are of type `number`! + +```typescript +const answer = 42 +let score = 98 +``` + +# TypeScript inference is quite good! + +```typescript +const name = 'Mary' +const students = ['Mary', 'Steven', 'Paulo', 'Sophia'] +const scores = [98, 100, 55, 100] +``` + +- `name` is a string +- `students` is an array of strings +- `scores is an array of numbers + +We can see this over in Visual Studio if we have a TypeScript file! + +--- + +# Mix and Match + +What about an array that has different types of elements? + +Unlike `C#`, TypeScript can handle that just fine, and in a very nice way. + +![fit right](http://3.bp.blogspot.com/-2vVZ7SPGpIg/UFs0TJ5zTdI/AAAAAAAABOA/qT7jRACFolE/s320/IMG_1154.JPG) + +--- + +# Mix and Match + +```typescript +const differentKindsOfThings = [42, 'Ice Cream', 100, 'Tacos'] +``` + + The variable differentKindsOfThings + will have the type + + (string | number)[] + +The `|` is a `union` of types; it means **or**. + +`differentKindsOfThings` is an array of elements that can be **either** a `string` **or** a `number`. + +--- + +# Declaring types explicitly + +```typescript +const name: string = 'Mary' + +const students: string[] = ['Mary', 'Steven', 'Paulo', 'Sophia'] + +const scores: number[] = [98, 100, 55, 100] + +const differentKindsOfThings: (string | number)[] = [ + 42, + 'Ice Cream', + 100, + 'Tacos', +] +``` + +--- + +# Declaring types explicitly + +Once we introduce the idea of TypeScript objects we'll discuss why specifying an explict type is useful. + +--- + # Undefined variables After declaring a variable but before assigning it a value, the variable will contain a special but confusing, value known as `undefined` -```javascript -let name // name contains 'undefined' +The type of the variable will also be `any` which means it will accept a value of **any** kind. + +```typescript +let name // name contains 'undefined', and is of `any` type name = 'Jane' // name now contains the value 'Jane' ``` --- -# Good variable hygiene +## Avoid `undefined` and `any` in your code! -Follow these rules, and you'll do well: - -- Only use `const` and `let`. -- Use `const` unless you have a **good** reason to use `let` -- Always initialize a variable unless you have a **good** reason not to. -- Avoid assigning a **different** type to a variable once created. +In fact, we can turn on code checking tools to make sure we avoid them! --- -# Dynamically typed +## Bad form -That means you don't have to specify the data type of a variable when you declare it. +```typescript +let name +// name contains 'undefined' and is of type `any` -JavaScript doesn't care what type of value a variable has, unlike `C#` +name = 'Jane' +// name now contains the value 'Jane', but `name` is still an `any` type. +``` --- -# Valid code +## Better form + +```typescript +let name: string +// name contains 'undefined' and should be of type `string` -```javascript -let answer = 42 +name = 'Jane' +// name now contains the value 'Jane' ``` -And later, you could assign the same variable a string value, for example: +--- + +## Best form -```javascript -answer = 'Thanks for all the fish...' +```typescript +const name = 'Jane' +// name contains the value 'Jane' and we avoid any issue with `undefined` ``` --- +# Good variable hygiene + +Follow these rules, and you'll do well: + +- Only use `const` and `let`. +- Use `const` unless you have a **good** reason to use `let` +- Always initialize a variable unless you have a **good** reason not to. + +--- + # Conversions -JavaScript is far more forgiving when converting types. +TypeScript is far more forgiving when converting types. -Valid in `JavaScript` but not allowed in a language like `C#` +Valid in `TypeScript` but not allowed in a language like `C#` -```javascript -let x = 'The answer is ' + 42 // "The answer is 42" -let y = 42 + ' is the answer' // "42 is the answer" +```typescript +const x = 'The answer is ' + 42 // "The answer is 42" +const y = 42 + ' is the answer' // "42 is the answer" ``` --- @@ -227,18 +287,20 @@ let y = 42 + ' is the answer' // "42 is the answer" # Doesn't work as you might expect -In statements involving other operators, JavaScript does not convert numeric values to strings. For example: +In statements involving other operators, TypeScript does not convert numeric values to strings. For example: + +```typescript +let x: string -```javascript -'37' - 7 // 30 -'37' + 7 // "377" +x = '37' - 7 // 30 and notes an error +x = '37' + 7 // "377" does not note any error ``` --- # String interpolation -```javascript +```typescript const score = 98 const answer = 42 @@ -251,17 +313,17 @@ const message = `Congratulations, ${answer} is correct. You have ${score} points Similar to other languages as a combination of state and behavior. -In JavaScript, an object is a standalone entity with properties and type. Compare it with a cup, for example. A cup is an object with properties. A cup has a color, a design, weight, and a material. In the same way, JavaScript objects can have properties, which define their characteristics. +In TypeScript, an object is a standalone entity with properties and type. Compare it with a cup, for example. A cup is an object with properties. A cup has a color, a design, weight, and a material. In the same way, TypeScript objects can have properties, which define their characteristics. --- # Object Properties -A JavaScript object has properties associated with it. +A TypeScript object has properties associated with it. A property of an object is a variable that is attached to the object. -Object properties are the same as ordinary JavaScript variables, except for the attachment to objects. +Object properties are the same as ordinary TypeScript variables, except for the attachment to objects. The properties of an object define the characteristics of the object. @@ -271,7 +333,7 @@ The properties of an object define the characteristics of the object. You access the properties of an object with a simple dot-notation: -```javascript +```typescript objectName.propertyName ``` @@ -283,7 +345,7 @@ objectName.propertyName [.column] -Like all JavaScript variables, both the object name (which could be a standard variable) and property name are case-sensitive. +Like all TypeScript variables, both the object name (which could be a standard variable) and property name are case-sensitive. You can define a property by assigning it a value. @@ -291,20 +353,7 @@ For example, let's create an object named myCar and give it properties named mak [.column] -```javascript -const myCar = new Object() -myCar.make = 'Ford' -myCar.model = 'Mustang' -myCar.year = 1969 -``` - ---- - -# Object Initializer - -The previous example could also use an object initializer, which is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}): - -```javascript +```typescript const myCar = { make: 'Ford', model: 'Mustang', @@ -314,13 +363,9 @@ const myCar = { --- -# Object unassigned properties - -> Unassigned properties of an object are `undefined` (and not `null`). +# Object Initializer -```javascript -myCar.color // undefined -``` +The previous example uses an object initializer, which is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}): --- @@ -328,13 +373,13 @@ myCar.color // undefined [.column] -Properties of JavaScript objects can also be accessed or set using a bracket notation. +Properties of TypeScript objects can also be accessed or set using a bracket notation. So, for example, you could access the properties of the `myCar` object as follows: [.column] -```javascript +```typescript myCar['make'] = 'Ford' myCar['model'] = 'Mustang' myCar['year'] = 1969 @@ -344,61 +389,188 @@ myCar['year'] = 1969 # Why? -An object property name can be any valid JavaScript string or anything convertible into a string, including the empty string. +An object property name can be any valid TypeScript string or anything convertible into a string, including the empty string. -> However, any property name that is not a valid JavaScript identifier (for example, a property name with space or hyphens, or that starts with a number) can only use the square bracket notation. +> However, any property name that is not a valid TypeScript identifier (for example, a property name with space or hyphens, or that starts with a number) can only use the square bracket notation. --- -# Objects +# Make a second car -**LOTS** more in the handbook! Please consider reading the entire `Intro to JavaScript` and trying out the code for yourself. +```typescript +const theirCar = { + make: 'Jeep', + model: 'Wrangler', + year: 2021, +} +``` + +This car has the same `type` as the `myCar` variable, but only through coincidence. --- -# Arrays +# Make another car -JavaScript also has an array type. JavaScript arrays are more flexible than C#'s and are more akin to `List<>` in their flexibility. JavaScript arrays also differ from `C#` arrays as JavaScript arrays can store values of different types. +```typescript +const otherCar = { + make: 'Honda', + modal: 'Fit', + year: 2020, +} +``` -There are three ways to declare an array: +Anyone see a problem? + +--- + +# Typo! -```javascript -let array = new Array(element0, element1, ..., elementN); -let array = Array(element0, element1, ..., elementN); -let array = [element0, element1, ..., elementN]; +```typescript +const otherCar = { + make: 'Honda', + modal: 'Fit', + year: 2020, +} +``` + +I make **many** typos and similar errors. Maybe TypeScript can help me? + +--- + +# Using types to find issues in our code + +We can teach TypeScript about a new specific type and give it a name of our choice. + +```typescript +type Car = { + make: string + model: string + year: number +} +``` + +--- + +# Using the type + +[.column] + +```typescript +type Car = { + make: string + model: string + year: number +} + +const myCar: Car = { + make: 'Ford', + model: 'Mustang', + year: 1969, +} +``` + +[.column] + +```typescript +const theirCar: Car = { + make: 'Jeep', + model: 'Wrangler', + year: 2021, +} + +const otherCar: Car = { + make: 'Honda', + modal: 'Fit', + year: 2020, +} +``` + +# [fit] The `modal` error will be called out + +--- + +# Making a new object from an existing one + +[.column] + +Let's take our car example again: + +```typescript +const myCar = { + make: 'Ford', + model: 'Mustang', + year: 1969, +} +``` + +[.column] + +Make a new car, but with a different year. + +```typescript +const myOtherCar = { + make: myCar.make, + model: myCar.model, + year: 1971, +} +``` + +--- + +# Fortunately, TypeScript allows for a shortcut + +- "expands" all the keys and values. +- This is known as the `spread` operator and is noted as `...` + +```typescript +const myOtherCar = { + ...myCar, + year: 1971, +} ``` --- # Arrays -The `new Array` and `Array()` styles are confusing since they have a second form that creates an array with a sequence of empty elements +TypeScript also has an array type. TypeScript arrays are more flexible than C#'s and are more akin to `List<>` in their flexibility. TypeScript arrays also differ from `C#` arrays as TypeScript arrays can store values of different types. + +There are three ways to declare an array: -```javascript -let arrayWithSevenEmptyElements = new Array(7) -let arrayWithSevenEmptyElements = Array(7) +```typescript +const array = new Array(element0, element1, ..., elementN); +const array = Array(element0, element1, ..., elementN); +const array = [element0, element1, ..., elementN]; ``` -For this reason, we typically use the `literal` form. +--- + +# `Array` and `new Array` are problematic + +- `const array = new Array('hello', 42)` will define an array of `string` and convert the `42` to a string. +- `const array = new Array(42, 'hello')` will be a TypeScript error. +- These are not recommended approaches for creating arrays. --- # Array literals -```javascript -let people = ['Betty', 'Wilma', 'Fred', 'Barny'] -let scores = [100, 42, 50, 98] -let collection = ['Betty', 98, 'Fred', 12, 42] +```typescript +const people = ['Betty', 'Wilma', 'Fred', 'Barny'] +const scores = [100, 42, 50, 98] +const collection = ['Betty', 98, 'Fred', 12, 42] ``` +Even though the arrays contain different types, TypeScript will create the correct kind of array. + --- # Populating an array We can also use the `[]` operator to assign values to specific elements of an array. -```javascript -let employees = [] +```typescript +const employees = [] employees[0] = 'Casey Jones' employees[1] = 'Phil Lesh' employees[2] = 'August West' @@ -406,29 +578,22 @@ employees[2] = 'August West' --- -# Populating an array +# Wait, what about const? -We can set elements of an array even if they are not next to each other. JavaScript will fill in the elements in between with empty items. +`const` only refers to the **variable**, not it's contents. -[.column] +It only prevents us from doing `employees = ['Peter', 'Paul', 'Mary']` -```javascript -let employees = [] -employees[0] = 'Casey Jones' -employees[12] = 'Phil Lesh' -employees[42] = 'August West' -``` +--- -[.column] +# Can you make an unchangable array? -```javascript -[ - 'Casey Jones', - <11 empty items>, - 'Phil Lesh', - <29 empty items>, - 'August West' -] +- Yes, use `ReadOnlyArray<>` + +```typescript +const cantChangeTheseValues: ReadonlyArray = [42, 100, 52] + +cantChangeTheseValues[0] = 1 ``` --- @@ -437,7 +602,7 @@ employees[42] = 'August West' A common operation is to iterate over the values of an array, processing each one in some way. The simplest way to do this is as follows: -```javascript +```typescript let colors = ['red', 'green', 'blue'] for (let index = 0; index < colors.length; index++) { console.log(colors[index]) @@ -450,7 +615,7 @@ for (let index = 0; index < colors.length; index++) { The `forEach()` method provides another way of iterating over an array: -```javascript +```typescript let colors = ['red', 'green', 'blue'] colors.forEach(function (color) { console.log(color) @@ -464,7 +629,7 @@ colors.forEach(function (color) { Alternatively, You can shorten the code for the forEach parameter with Arrow Functions: -```javascript +```typescript let colors = ['red', 'green', 'blue'] colors.forEach(color => console.log(color)) ``` @@ -479,9 +644,9 @@ For more details on how to manipulate arrays, including adding and removing elem # Control flow -Control flow in JavaScript is nearly identical to `C#` in that our code consists of a sequential set of statements that comprises a block of code: +Control flow in TypeScript is nearly identical to `C#` in that our code consists of a sequential set of statements that comprises a block of code: -```javascript +```typescript { statement_1 statement_2 @@ -498,7 +663,7 @@ Control flow in JavaScript is nearly identical to `C#` in that our code consists We can control the flow of the code with a conditional statement: -```javascript +```typescript if (condition) { statement_1 } else { @@ -512,9 +677,9 @@ This works exactly as `C#` except for the different style in how the braces and # Comparisons -The same boolean comparisons are present in `JavaScript`, `<`, `>`, `<=`, `>=`, and `==`. +The same boolean comparisons are present in `TypeScript`, `<`, `>`, `<=`, `>=`, and `==`. -```javascript +```typescript const answer = 42 const score = 98 @@ -552,12 +717,14 @@ It first sees if the values can be converted to a _common type_ and then perform # WAT -```javascript +```typescript const answer = 42 const message = '42' if (answer == message) { // Yup! this will be *TRUE* + // + // However, TypeScript will complain at us! } ``` @@ -578,7 +745,7 @@ if (answer == message) { --- -# In JavaScript +# In TypeScript | | | | ----- | ------------------------------ | @@ -591,7 +758,7 @@ if (answer == message) { In **most** cases `===` is what you want when comparing values. -There are some exceptions, and we'll discuss them along the way. +There are some exceptions, but they are **very** rare. --- @@ -603,7 +770,7 @@ See [this article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equal # For loop -```javascript +```typescript for(let index = 0; index < 20; index++) { console.log(`The index is ${index}) } @@ -613,7 +780,7 @@ for(let index = 0; index < 20; index++) { # Switch -```javascript +```typescript switch (expression) { case label_1: statements_1 @@ -634,62 +801,56 @@ switch (expression) { # Functions -In JavaScript, named groups of code that perform a specific behavior are called `functions`. (whereas in `C#` we called them `methods`) +In TypeScript, named groups of code that perform a specific behavior are called `functions`. (whereas in `C#` we called them `methods`) Also, these `functions` do not need to be in `classes`. -In fact, the idea of `class`es came to JavaScript quite late. - --- # Example -The following code defines a simple function named square: +The following code defines a simple function named greet: -```javascript -// Function keyword +```typescript +// function keyword // | -// | name of method -// | | -// | | list of arguments -// | | | -// | | | -// v v v -function square(number) { - return number * number +// | name of the function +// | | +// | | required parenthesis where arguments will go +// | | | +// | | | opening scope of the function +// | | | | +// | | | | +// v v v v +function greet() { + console.log('Hello there programmer!') } ``` --- -# Things to notice +# Add arguments -```javascript -function square(number) { - return number * number +For example, the following code defines a simple function named +square. + +```typescript +// argument type +// | +// | function return type (optional) +// | | +// | | +// v v +function square(valueToSquare: number): number { + return valueToSquare * valueToSquare } ``` -- No variable types for arguments -- No return type declaration - ---- - -# Duck typing - -> If it quacks like a duck, and it walks like a duck, it must be a duck - -Our `square` method will work for any variable type where `*`behaves the way we like. - ---- - -> With great power comes great responsibility. - --- # Calling functions -```javascript +```typescript const answer = square(5) ``` @@ -707,9 +868,9 @@ const answer = square(5) While the function declaration above is syntactically a statement, functions can also use a `function expression` style. -```javascript -const square = function (number) { - return number * number +```typescript +const square = function (valueToSquare: number) { + return valueToSquare * valueToSquare } const answer = square(4) // answer gets the value 16 @@ -719,58 +880,85 @@ const answer = square(4) // answer gets the value 16 # Functions are just another kind of type! -We say that `functions` in JavaScript are a type just like numbers, strings and booleans. +We say that `functions` in TypeScript are a type just like numbers, strings and booleans. We can assign them names and pass them as arguments. --- -[.column] +```typescript +type PrintItFunction = (value: number) => number -```javascript -function printIt(array, func) { - for (let index = 0; index < array.length; index++) { - const value = array[index] +function printIt(numbers: number[], func: PrintItFunction) { + for (let index = 0; index < numbers.length; index++) { + const value = numbers[index] const result = func(value) - console.log(`Function turned ${value} into ${result}`) + console.log(`Turned ${value} into ${result}`) } } +``` -const square = function (number) { - return number * number -} +--- -const double = function (number) { - return number * 2 +```typescript +function square(valueToSquare: number) { + return valueToSquare \* valueToSquare } -``` -[.column] +function double(valueToDouble: number) { + return valueToDouble * 2 +} -```javascript const numbers = [1, 2, 3, 4, 5] printIt(numbers, square) -// Function turned 1 into 1 -// Function turned 2 into 4 -// Function turned 3 into 9 -// Function turned 4 into 16 -// Function turned 5 into 25 +// Turned 1 into 1 +// Turned 2 into 4 +// Turned 3 into 9 +// Turned 4 into 16 +// Turned 5 into 25 printIt(numbers, double) -// Function turned 1 into 2 -// Function turned 2 into 4 -// Function turned 3 into 6 -// Function turned 4 into 8 -// Function turned 5 into 10 +// Turned 1 into 2 +// Turned 2 into 4 +// Turned 3 into 6 +// Turned 4 into 8 +// Turned 5 into 10 +``` + +--- + +## Here is where TypeScript shines! + +What if we define a new function that doesn't fit the pattern our `printIt` function expects. + +```typescript +function upperCase(stringToUpperCase: string) { + return stringToUpperCase.toUpperCase() +} + +const words = ['hello', 'there'] +printIt(words, upperCase) ``` --- +The TypeScript system would immediately tell us that `words` isn't an array of numbers and cannot be sent to `printIt`! + +![inline](./assets/printit-error.png) + +--- + +If we "fix" this error by using our `numbers` variable, we'll see that TypeScript then notifies us that the `upperCase` doesn't follow the style of the `function` we are expecting! + +![inline](./assets/printit-error-2.png) + +--- + # Powerful -Passing functions as arguments to other functions is a very powerful pattern in JavaScript. We will be using this ability quite a bit in other lessons. +Passing functions as arguments to other functions is a very powerful pattern in TypeScript. We will be using this ability quite a bit in other lessons. -Functions treated as values for variables and passed as arguments are two things that make JavaScript a **functional**-style language. +Functions treated as values for variables and passed as arguments are two things that make TypeScript a **functional**-style language. --- @@ -784,7 +972,7 @@ A function defined inside another function can also access all variables defined --- -```javascript +```typescript const PI = 3.14 const numbers = [1, 2, 4, 8, 16] @@ -816,7 +1004,7 @@ Taking the above example another step, we'll introduce the concept of `closures` # A Closure -Closures in JavaScript are a way to create a function that has access to the variables and functions defined in the outer scope. +Closures in TypeScript are a way to create a function that has access to the variables and functions defined in the outer scope. What does this mean? We can try a few examples. @@ -824,7 +1012,7 @@ What does this mean? We can try a few examples. ## Simple example -```javascript +```typescript const variableFromOuterScope = "Wow, I'm from the outer scope" function thisFunctionActsLikeAClosure() { @@ -869,12 +1057,12 @@ We can create a more complex example to demonstrate that these functions do "rem # Doing work later but still having access to variables - Create an array of people. Each person will have a name, a birthday, and a number of milliseconds we should wait before showing their information. -- Use javaScript's `setTimeout` to do the waiting. +- Use TypeScript's `setTimeout` to do the waiting. - Since `setTimeout` calls a function **later**, this will help prove that the function is really "remembering" its values. --- -```javaScript +```typescript const people = [ { name: 'Alan Turing', @@ -897,7 +1085,6 @@ const people = [ delayMilliseconds: 2500, }, ] - ``` --- @@ -906,7 +1093,7 @@ const people = [ Then we will create a method that accepts a person variable and prints out details about them. -```javascript +```typescript function printPersonInfo(person) { console.log(`${person.name} was born on ${person.birthDate}`) } @@ -923,7 +1110,7 @@ function printPersonInfo(person) { --- -```javascript +```typescript people.forEach(function (person) { // Inside here we have access to the `person` variable here. The `person` variable is // recreated each time through the forEach loop. Since it is an argument to the diff --git a/lessons/js-intro/objects.md b/lessons/js-intro/objects.md index c223602c..0f276610 100644 --- a/lessons/js-intro/objects.md +++ b/lessons/js-intro/objects.md @@ -220,9 +220,9 @@ const myOtherCar = { And now we would have a new object, independent of the first, with a different year. However, you could imagine that if we had many properties it would be -cumbersome to repeat all the keys and values. Fortunately, recent TypeScript -allows for a shortcut to "expand" all the keys and values. This is known as the -`spread` operator and is noted as `...` +cumbersome to repeat all the keys and values. Fortunately, TypeScript allows for +a shortcut to "expand" all the keys and values. This is known as the `spread` +operator and is noted as `...` So let's write this again using the spread operator: From bec48e308981b88ece52667b5d7f64bf6323339c Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Sun, 18 Jul 2021 16:15:08 -0400 Subject: [PATCH 03/13] Updates TypeScript intro --- lessons/js-intro/index.md | 44 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/lessons/js-intro/index.md b/lessons/js-intro/index.md index 1cc43bfc..a4d2bb8b 100644 --- a/lessons/js-intro/index.md +++ b/lessons/js-intro/index.md @@ -1,5 +1,5 @@ --- -title: Welcome to JavaScript +title: Welcome to TypeScript assignments: - variables-js tags: @@ -7,50 +7,32 @@ tags: order: 1 --- -# Learning Objectives +## TypeScript -- What is JavaScript? -- How does JavaScript fit in the web? -- What is a line of code? -- Basic JavaScript operations. -- Basic JavaScript types (strings and numbers). -- What are variables and how to use them. -- What is a function? -- What is the DOM? -- How to listen to events? - -## JavaScript - -JavaScript is a language used to make webpages interactive (e.g. having complex +TypeScript is a language used to make webpages interactive (e.g. having complex animations, clickable buttons, popup menus, etc.). There are also more advanced -server side versions of JavaScript such as [Node.js](https://nodejs.org) which -allow you to add more functionality to a website than simply downloading files -(such as realtime collaboration between multiple computers). +server side versions of TypeScript which allow you to add more functionality to +a website than simply downloading files (such as realtime collaboration between +multiple computers). -In this lesson we will discuss client (browser) side JavaScript. +In this lesson we will discuss client (browser) side TypeScript. -Client-side JavaScript extends the core language by supplying objects to control +Client-side TypeScript extends the core language by supplying objects to control a browser and its Document Object Model (DOM). For example, client-side extensions allow an application to place elements on an HTML form and respond to user events such as mouse clicks, form input, and page navigation. This means -that in the browser, JavaScript can change the way the webpage (DOM) looks. - -JavaScript should not be confused with the Java programming language. Both -"Java" and "JavaScript" are trademarks or registered trademarks of Oracle in the -U.S. and other countries. However, the two programming languages are -significantly different in their syntax, semantics, and uses. Remember, "Java" -is to "JavaScript" like "car" is to "carpet". +that in the browser, TypeScript can change the way the webpage (DOM) looks. ## Document Object Model -We can use the power of JavaScript to make our webpages dynamic and powerful. -JavaScript interacts with our webpage through what is called the Document Object +We can use the power of TypeScript to make our webpages dynamic and powerful. +TypeScript interacts with our webpage through what is called the Document Object Model (DOM). The Document Object Model (DOM) connects web pages to scripts or programming languages by representing the structure of a document—such as the HTML -representing a web page—in memory. Usually that means JavaScript, although -modelling HTML, SVG, or XML documents as objects is not part of the JavaScript +representing a web page—in memory. Usually that means TypeScript, although +modelling HTML, SVG, or XML documents as objects is not part of the TypeScript language, as such. The DOM represents a document with a logical tree. Each branch of the tree ends From c8794538987a0410a8f49d429966e619a40946e4 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Sun, 18 Jul 2021 16:15:17 -0400 Subject: [PATCH 04/13] Adds js-intro lecture slides --- web/static/lectures/js-intro-lecture.pdf | Bin 132 -> 132 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web/static/lectures/js-intro-lecture.pdf b/web/static/lectures/js-intro-lecture.pdf index 13e04aed64e181481b79e46490394c1c2e1e856d..75046397e95e4573286ade2c2c9cddd082a9fe2e 100644 GIT binary patch delta 85 zcmV~$!3}^g2nEpe+9@1?mO^0(ccAf;c=qPG130qp?Xt_K=OA#d?$MK39hX^CQ!IsW ekcG2b<`5p35|oDNgiH;Biqz+RaZGnzLi#b+cDv@db7gB%-v(`QD7jW fA>oBV@G3&$gzh$6(Ma%jRA}V;dd^LoN;l3Q7i1T| From 3ba21aafd2fa83f40a3d4657384c798221952c24 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Sun, 18 Jul 2021 16:24:24 -0400 Subject: [PATCH 05/13] Update JavaScript => TypeScript --- lessons/js-intro/arrays.md | 16 ++++++++-------- lessons/js-intro/basics.md | 6 +++--- lessons/js-intro/closures.md | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lessons/js-intro/arrays.md b/lessons/js-intro/arrays.md index 15aff2e0..a1ff4352 100644 --- a/lessons/js-intro/arrays.md +++ b/lessons/js-intro/arrays.md @@ -12,7 +12,7 @@ employee number one, `employee[2]` employee number two, and so on. The following statements create equivalent arrays: -```javascript +```typescript const array = new Array(element0, element1, ..., elementN); const array = Array(element0, element1, ..., elementN); const array = [element0, element1, ..., elementN]; @@ -36,7 +36,7 @@ To create an array with non-zero length, but without any items, the following can be used. Note we need to give the `array` variable a type since we have no initial values. -```javascript +```typescript const array: number[] = [] array.length = arrayLength ``` @@ -45,7 +45,7 @@ array.length = arrayLength You can populate an array by assigning values to its elements. For example, -```javascript +```typescript const employees: string[] = [] employees[0] = 'Casey Jones' employees[1] = 'Phil Lesh' @@ -72,7 +72,7 @@ cantChangeTheseValues[0] = 1 You refer to an array's elements by using the element's ordinal number. For example, suppose you define the following array: -```javascript +```typescript const myArray = ['Wind', 'Rain', 'Fire'] ``` @@ -86,7 +86,7 @@ element of the array as `myArray[1]`. A common operation is to iterate over the values of an array, processing each one in some way. The simplest way to do this is as follows: -```javascript +```typescript const colors = ['red', 'green', 'blue'] for (let index = 0; index < colors.length; index++) { console.log(colors[index]) @@ -98,7 +98,7 @@ through the loop. The `forEach()` method provides another way of iterating over an array: -```javascript +```typescript const colors = ['red', 'green', 'blue'] colors.forEach(function (color) { console.log(color) @@ -108,7 +108,7 @@ colors.forEach(function (color) { Alternatively, You can shorten the code for the forEach parameter with Arrow Functions: -```javascript +```typescript const colors = ['red', 'green', 'blue'] colors.forEach(color => console.log(color)) ``` @@ -123,7 +123,7 @@ as a second argument in our arrow function. Note that we do not need to apply a type to `color` or to `index`. TypeScript will determine that they must be of types `string` and `number` respectively. -```javascript +```typescript const colors = ['red', 'green', 'blue'] colors.forEach((color, index) => console.log(`The color at position ${index} is ${color}`) diff --git a/lessons/js-intro/basics.md b/lessons/js-intro/basics.md index 62d635f7..53293a8d 100644 --- a/lessons/js-intro/basics.md +++ b/lessons/js-intro/basics.md @@ -3,11 +3,11 @@ title: The Basics order: 2 --- -JavaScript programs are made up from a series of instructions called +TypeScript programs are made up from a series of instructions called `statements`. These instructions are read by the computer (the browser) from the top to the bottom and from left to right. -In many JavaScript programs you will see that each statement ends with a +In many TypeScript programs you will see that each statement ends with a semicolon (`;`). While not absolutely necessary, some teams will use this style. At SDG we follow a style where the semicolons are not required. In fact we recommend you use an automatic code formatter named @@ -28,7 +28,7 @@ the author, or other readers, to the purpose of the code. Comments look like this: -```javascript +```TypeScript // This is a single line comment /* This is a longer comment diff --git a/lessons/js-intro/closures.md b/lessons/js-intro/closures.md index d8fe90ed..7b4f5f9f 100644 --- a/lessons/js-intro/closures.md +++ b/lessons/js-intro/closures.md @@ -5,16 +5,16 @@ order: 6 ## Closures -> NOTE: "What is a closure in JavaScript?" is often an interview question. +> NOTE: "What is a closure in TypeScript?" is often an interview question. -Closures in JavaScript are a way to create a function that has access to the +Closures in TypeScript are a way to create a function that has access to the variables and functions defined in the outer scope. What does this mean? We can try a few examples. ## Simple example -```javascript +```typescript const variableFromOuterScope = "Wow, I'm from the outer scope" function thisFunctionActsLikeAClosure() { @@ -48,11 +48,11 @@ do "remember" their values. First, we will create an array of people. Each person will have a name, a birthday, and a number of milliseconds we should wait before showing their -information. We'll use javaScript's `setTimeout` to do the waiting. Since +information. We'll use TypeScript's `setTimeout` to do the waiting. Since `setTimeout` calls a function **later** this will help prove that the function is really "remembering" its values. -```javascript +```typescript const people = [ { name: 'Alan Turing', @@ -80,7 +80,7 @@ const people = [ Then we will create a method that accepts a person variable and prints out details about them. -```javascript +```typescript function printPersonInfo(person) { console.log(`${person.name} was born on ${person.birthDate}`) } @@ -91,7 +91,7 @@ each person in the array. However, we will call this method from inside a call to `setTimeout`. `setTimeout` is a function that creates a timer that will call the supplied function later. -```javascript +```typescript people.forEach(function (person) { // Inside here we have access to the `person` variable here. The `person` variable is // recreated each time through the forEach loop. Since it is an argument to the From 5b3c371935a65afbd274715162dc3817e83757cf Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Mon, 26 Jul 2021 14:43:00 -0400 Subject: [PATCH 06/13] Adds more typescript to js-intro --- lessons/js-intro/closures.md | 13 ++++++++++--- lessons/js-intro/control-flow.md | 32 ++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lessons/js-intro/closures.md b/lessons/js-intro/closures.md index 7b4f5f9f..419095f0 100644 --- a/lessons/js-intro/closures.md +++ b/lessons/js-intro/closures.md @@ -5,7 +5,8 @@ order: 6 ## Closures -> NOTE: "What is a closure in TypeScript?" is often an interview question. +> NOTE: "What is a closure in JavaScript/TypeScript?" is often an interview +> question. Closures in TypeScript are a way to create a function that has access to the variables and functions defined in the outer scope. @@ -53,7 +54,13 @@ information. We'll use TypeScript's `setTimeout` to do the waiting. Since is really "remembering" its values. ```typescript -const people = [ +type Person = { + name: string + birthDate: string + delayMilliseconds: number +} + +const people: Person[] = [ { name: 'Alan Turing', birthDate: 'June 23, 1912', @@ -81,7 +88,7 @@ Then we will create a method that accepts a person variable and prints out details about them. ```typescript -function printPersonInfo(person) { +function printPersonInfo(person: Person) { console.log(`${person.name} was born on ${person.birthDate}`) } ``` diff --git a/lessons/js-intro/control-flow.md b/lessons/js-intro/control-flow.md index 15a007fa..caef6179 100644 --- a/lessons/js-intro/control-flow.md +++ b/lessons/js-intro/control-flow.md @@ -11,18 +11,34 @@ Before we can discuss control flow, we need to understand the idea of a block. ## Block -The most basic statement is a block statement that is used to group statements. -The block is delimited by a pair of curly brackets: +The most basic block is a block statement that is used to group statements. The +block is delimited by a pair of curly brackets: ```typescript { -statement_1 -statement_2 -. -. -. -statement_n + statement_1 + statement_2 + . + . + . + statement_n +} +``` + +Using a plain block like this can be used to reduce the "scope" of a variable. +That is, ensuring a variable only exists for a few lines of code. Here is an +example of when we would use this type of block: + +```typescript +const employees = ['Mary', 'Bob', 'Alice', 'Frank'] + +{ + let employeeIndex = 1 + + // Some code that uses the variable } + +// We want to ensure that the variable `employeeIndex` is not valid here. ``` ## Conditional Statements From 7d5c23cc38e1eff25e5aa58a356b34744c4b55c1 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Mon, 26 Jul 2021 14:53:54 -0400 Subject: [PATCH 07/13] Enumeration in typescript --- assignments/iteration-js.md | 18 ++++----- lessons/js-enumeration/enumeration.md | 56 +++++++++++++++++---------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/assignments/iteration-js.md b/assignments/iteration-js.md index 8d1f9dbf..25f34de2 100644 --- a/assignments/iteration-js.md +++ b/assignments/iteration-js.md @@ -1,6 +1,6 @@ --- -title: Iteration in JavaScript -tags: ['javascript', 'enumeration'] +title: Iteration in TypeScript +tags: ['typescript', 'enumeration'] --- ## Objectives @@ -10,14 +10,14 @@ tags: ['javascript', 'enumeration'] ## Instructions -1. Fork [this repository](https://github.com/suncoast-devs/js-iteration) to your own account. +1. Fork [this repository](https://github.com/suncoast-devs/ts-iteration) to your own account. 2. Change into your projects directory: -3. Clone your repository: `hub clone js-iteration` -4. Change into your project's directory: `cd js-iteration` +3. Clone your repository: `hub clone ts-iteration` +4. Change into your project's directory: `cd ts-iteration` 5. Install the dependencies: `npm install` 6. Open in your editor: `code .` 7. Start the test runner: `npm test` -8. In VS Code, open the file: `src/functions.js` and work on functions until tests pass. +8. In VS Code, open the file: `src/functions.ts` and work on functions until tests pass. 9. Commit and push your work to GitHub. ## Explorer Mode @@ -33,7 +33,7 @@ tags: ['javascript', 'enumeration'] - Your method must accept an array and a callback function. - Example for your `_map`: - ```js + ```typescript const numbers = [1, 2, 3, 4, 5] const doubled = _map(numbers, function (number) { @@ -44,7 +44,7 @@ tags: ['javascript', 'enumeration'] const increased = _map(numbers, function (number) { return number + 2 }) - // increated needs to be [2,3,4,5,6] + // increased needs to be [2,3,4,5,6] ``` - `map` @@ -58,7 +58,7 @@ tags: ['javascript', 'enumeration'] ## Additional Resources Reference the documentation on DevDocs to find what kind of helpful functions -might already be in JavaScript. +might already be in TypeScript. - [String Functions on DevDocs](https://devdocs.io/javascript/global_objects/string). - [Array Functions on DevDocs](http://devdocs.io/javascript/global_objects/array). diff --git a/lessons/js-enumeration/enumeration.md b/lessons/js-enumeration/enumeration.md index 8f174292..dc0a662e 100644 --- a/lessons/js-enumeration/enumeration.md +++ b/lessons/js-enumeration/enumeration.md @@ -9,7 +9,7 @@ When we [learned about arrays in JavaScript](/lessons/js-intro/arrays) we saw that the `forEach` method is very helpful for iterating through the contents of an array. -```javascript +```typescript const colors = ['red', 'green', 'blue'] colors.forEach(function (color, index) { console.log(`The color at position ${index} is ${color}`) @@ -36,7 +36,7 @@ corresponding index of the original array. That is: -```javascript +```typescript const colors = ['red', 'green', 'blue'] // Code here @@ -47,7 +47,7 @@ const lengths = [3, 5, 4] We will start by doing this in a very manual way. Begin by creating a new array to receive the individual elements. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -55,7 +55,7 @@ const lengths = [] Then we will setup the `forEach` loop -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -69,7 +69,7 @@ Now we will concentrate on the code inside the loop. Here we want to take the individual color and compute it's length. Then _append_ that to the `lengths` array. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -92,7 +92,7 @@ generic way. If we needed to have another array except the transformation is now the names of the colors in _UPPERCASE_ we would need to re-implement the entire loop. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -118,7 +118,7 @@ console.log(uppercased) // [ 'RED', 'GREEN', 'BLUE' ] To remedy this, JavaScript supplies a method with precisely this behavior: `map` -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(function (color) { @@ -146,7 +146,7 @@ Notice only a few small changes to our code. We can simplify the code a little if we remove the temporary variables. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(function (color) { @@ -164,7 +164,7 @@ console.log(uppercased) // [ 'RED', 'GREEN', 'BLUE' ] We can also reduce the code by changing to using `arrow functions` -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(color => { @@ -184,7 +184,7 @@ And now that we are using `arrow functions` we can apply a rule that allows us an even more concise syntax when the arrow function **only** contains a return statement. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(color => color.length) @@ -209,7 +209,7 @@ resemblance of the `Select` statement. In fact `Select` from C# and `map` from If we wish to create a new array but only retain _some_ of the elements from the original array we can use `filter` -```javascript +```typescript const colors = ['red', 'green', 'blue'] const longColors = colors.filter(color => color.length > 3) @@ -227,7 +227,7 @@ initial value. The reducing function takes at least two arguments itself, the first being the accumulator and the second being the current element from the array. -```javascript +```typescript const numbers = [100, 42, 13] const total = numbers.reduce((total, number) => total + number, 0) @@ -244,7 +244,7 @@ the **keys** of the object. We can use this array to `map`, and `filter`. For instance, suppose we were given the following object: -```javascript +```typescript const myHobbies = { pandas: { title: 'Panda Bears', @@ -265,13 +265,13 @@ by the title. That is given the object above we would want something like We can't do `myHobbies.map` but we can do this: -```javascript +```typescript const keys = Object.keys(myHobbies) // ['pandas', 'miniatures'] ``` And now we can use that to map -```javascript +```typescript const keys = Object.keys(myHobbies) // ['pandas', 'miniatures'] const answer = keys.map(key => { @@ -281,34 +281,48 @@ const answer = keys.map(key => { }) ``` +There is a downside to this approach. The variable `hobby` will be defined as +`any`. This is because TypeScript can't determine the type. + There is another way to work with objects and that is `Object.entries` -- `entries` gives us back an array-of-arrays. The first element of each array is the key, and the second is the value. This allows us to avoid the value lookup. -```javascript +```typescript const entries = Object.entries(myHobbies) // [['pandas', { title: ...., description: ...}], ['miniatures', { title: ..., description: ...}] const answer = entries.map(entry => { - return `${entry[0]} - ${entry[1].title}` + return `${entry[0]} - ${entry[1].title} ${entry[1].description}` }) ``` Using destructuring we can avoid the `entry[0]` and `entry[1]` code and give our variables better names: -```javascript +```typescript const entries = Object.entries(myHobbies) // [['pandas', { title: ...., description: ...}], ['miniatures', { title: ..., description: ...}] const answer = entries.map(([key, value]) => { - return `${key} - ${value.title}` + var title = value.title + var description = value.description + + return `${key} - ${title} ${description}` }) ``` And we can reduce the code a bit further: -```javascript +```typescript +const answer = Object.entries(myHobbies).map( + ([key, value]) => `${key} - ${value.title} ${value.description}` +) +``` + +We could also use a better name for the `value` variable: + +```typescript const answer = Object.entries(myHobbies).map( - ([key, value]) => `${key} - ${value.title}` + ([key, hobby]) => `${key} - ${hobby.title} ${hobby.description}` ) ``` From 36eefb68cd8c7c9eff4fb96d4a9c2eaaf1542c0b Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Mon, 26 Jul 2021 14:57:55 -0400 Subject: [PATCH 08/13] js enumeration lecture converted to typescript --- lessons/js-enumeration/lecture.md | 37 +++++++++--------- .../lectures/js-enumeration-lecture.pdf | Bin 131 -> 131 bytes 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lessons/js-enumeration/lecture.md b/lessons/js-enumeration/lecture.md index 17d6b975..2eceab61 100644 --- a/lessons/js-enumeration/lecture.md +++ b/lessons/js-enumeration/lecture.md @@ -8,11 +8,10 @@ theme: Next,1 JavaScript has `for` loops we can use for enumerating the elements of an array. -```javascript +```typescript const colors = ['red', 'green', 'blue'] -function logSomeColor(color, index) -{ +function logSomeColor(color: string, index: number) { console.log(`The color at position ${index} is ${color}`) } @@ -27,7 +26,7 @@ colors.forEach(logSomeColor) - Each element of this new array to be the equal to the **length** of the string at the corresponding index of the original array. -```javascript +```typescript [ [ 'red', => 3, 'green', => 5, @@ -37,7 +36,7 @@ colors.forEach(logSomeColor) --- -```javascript +```typescript const colors = ['red', 'green', 'blue'] // Code here @@ -55,7 +54,7 @@ We will start by doing this in a very manual way. Begin by creating a new array to receive the individual elements. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -65,7 +64,7 @@ const lengths = [] Then we will setup the `forEach` loop -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = [] @@ -81,10 +80,10 @@ colors.forEach(function (color) { [.code-highlight: 6-8] -```javascript +```typescript const colors = ['red', 'green', 'blue'] -const lengths = [] +const lengths: number[] = [] colors.forEach(function (color) { const lengthOfColor = color.length @@ -111,10 +110,10 @@ console.log(lengths) // [ 3, 5, 4 ] [.column] -```javascript +```typescript const colors = ['red', 'green', 'blue'] -const lengths = [] +const lengths: number[] = [] colors.forEach(function (color) { const lengthOfColor = color.length @@ -127,8 +126,8 @@ console.log(lengths) // [ 3, 5, 4 ] [.column] -```javascript -const uppercased = [] +```typescript +const uppercased: string[] = [] colors.forEach(function (color) { const uppercase = color.toUpperCase() @@ -145,7 +144,7 @@ console.log(uppercased) // [ 'RED', 'GREEN', 'BLUE' ] [.code-highlight: 3-8] -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(function (color) { @@ -159,7 +158,7 @@ console.log(lengths) // [ 3, 5, 4 ] --- -```javascript +```typescript const uppercased = colors.map(function (color) { const uppercase = color.toUpperCase() @@ -182,7 +181,7 @@ console.log(uppercased) // [ 'RED', 'GREEN', 'BLUE' ] We can simplify the code a little if we remove the temporary variables. -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(function (color) { @@ -198,7 +197,7 @@ console.log(lengths) // [ 3, 5, 4 ] We can also use `arrow functions` -```javascript +```typescript const colors = ['red', 'green', 'blue'] const lengths = colors.map(color => color.length) @@ -222,7 +221,7 @@ console.log(uppercased) // [ 'RED', 'GREEN', 'BLUE' ] If we wish to create a new array but only retain _some_ of the elements from the original array we can use `filter` -```javascript +```typescript const colors = ['red', 'green', 'blue'] const longColors = colors.filter(color => color.length > 3) @@ -238,7 +237,7 @@ This is very similar to `C# Where` from LINQ. ^ To use the classic example of adding up a list of numbers. Reduce takes at least two parameters, the first being the reducing function, and the second being the initial value. The reducing function takes at least two arguments itself, the first being the accumulator and the second being the current element from the array. -```javascript +```typescript const numbers = [100, 42, 13] const total = numbers.reduce((total, number) => total + number, 0) diff --git a/web/static/lectures/js-enumeration-lecture.pdf b/web/static/lectures/js-enumeration-lecture.pdf index 9f4773779e34a0fee214b180eb866c5bd02919b7..9ba79448912b82bb7894ec5efe2b88198a2f77d9 100644 GIT binary patch delta 84 zcmV~$yAgmO3;@uxWeP_KAptUkLxg$4#AN%P!AK7x0r6ivG ee;Q^EHsap0BTQ=w+QW6AIgr%*dg6eN(zJf<#umi@ From d54e5292486ed9341e1f12980d8fe350dd1dde50 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Mon, 26 Jul 2021 18:45:34 -0400 Subject: [PATCH 09/13] More typescript convert --- lessons/js-dom/index.md | 8 +- lessons/js-modules/index.md | 27 ++- lessons/js-modules/lecture.md | 21 ++- lessons/js-ui-as-state/index.md | 80 ++++++--- lessons/js-ui-as-state/lecture.md | 159 +++++++++++------- programs/web-development.yaml | 1 - web/static/lectures/js-modules-lecture.pdf | Bin 131 -> 131 bytes .../lectures/js-ui-as-state-lecture.pdf | Bin 131 -> 131 bytes 8 files changed, 199 insertions(+), 97 deletions(-) diff --git a/lessons/js-dom/index.md b/lessons/js-dom/index.md index c550b48a..fae8b865 100644 --- a/lessons/js-dom/index.md +++ b/lessons/js-dom/index.md @@ -1,14 +1,14 @@ --- -title: JavaScript and the DOM +title: TypeScript and the DOM assignment: - scoreboard tags: - mdn-content --- -In this lesson we will learn how to use JavaScript code to interact with our web -pages. Without JavaScript our browsers cannot manipulate page beyond the static +In this lesson we will learn how to use TypeScript code to interact with our web +pages. Without TypeScript our browsers cannot manipulate page beyond the static version present when the page is first loaded. -We will learn how to access the `Document Object Model` and use JavaScript to +We will learn how to access the `Document Object Model` and use TypeScript to read, add, change, and remove elements from it. diff --git a/lessons/js-modules/index.md b/lessons/js-modules/index.md index 79390c11..3d8ff83f 100644 --- a/lessons/js-modules/index.md +++ b/lessons/js-modules/index.md @@ -1,5 +1,5 @@ --- -title: JavaScript Modules +title: TypeScript Modules --- --- @@ -30,9 +30,9 @@ Modules help us to: --- -## Modules in JavaScript +## Modules in TypeScript -^ Prior to a few years ago, no support for modules existed in the JavaScript +^ Prior to a few years ago, no support for modules existed in the TypeScript language. --- @@ -74,11 +74,12 @@ The short answer? [Kind of.](https://caniuse.com/#feat=es6-module) ## What does a module look like? -```javascript +```typescript // lib/randomInteger.js -const randomInteger = (min, max) => { +function randomInteger(min: number, max: number) { min = Math.ceil(min) max = Math.floor(max) + return Math.floor(Math.random() * (max - min)) + min } @@ -95,11 +96,19 @@ console.log(`You just rolled a ${role}!`) ## Modules can export more than one thing -```javascript +```typescript // lib/util.js -export const squareRoot = Math.sqrt -export const square = x => x * x -export const diagonalLength = (x, y) => squareRoot(square(x) + square(y)) +export function squareRoot(number: number) { + return Math.sqrt(number) +} + +export function square(x: number) { + return x * x +} + +export function diagonalLength(x: number, y: number) { + return squareRoot(square(x) + square(y)) +} // main.js import { diagonalLength } from './lib/util' diff --git a/lessons/js-modules/lecture.md b/lessons/js-modules/lecture.md index f677d0e5..9ee2aad4 100644 --- a/lessons/js-modules/lecture.md +++ b/lessons/js-modules/lecture.md @@ -74,11 +74,12 @@ The short answer? [Kind of.](https://caniuse.com/#feat=es6-module) ## What does a module look like? -```javascript +```typescript // lib/randomInteger.js -const randomInteger = (min, max) => { +function randomInteger(min: number, max: number) { min = Math.ceil(min) max = Math.floor(max) + return Math.floor(Math.random() * (max - min)) + min } @@ -95,11 +96,19 @@ console.log(`You just rolled a ${role}!`) ## Modules can export more than one thing -```javascript +```typescript // lib/util.js -export const squareRoot = Math.sqrt -export const square = x => x * x -export const diagonalLength = (x, y) => squareRoot(square(x) + square(y)) +export function squareRoot(number: number) { + return Math.sqrt(number) +} + +export function square(x: number) { + return x * x +} + +export function diagonalLength(x: number, y: number) { + return squareRoot(square(x) + square(y)) +} // main.js import { diagonalLength } from './lib/util' diff --git a/lessons/js-ui-as-state/index.md b/lessons/js-ui-as-state/index.md index 46dcf50c..055f7211 100644 --- a/lessons/js-ui-as-state/index.md +++ b/lessons/js-ui-as-state/index.md @@ -4,8 +4,8 @@ assignment: - roshambo-js --- -In [the lesson on using JavaScript to modify the DOM](/lessons/js-dom) we -discussed how to use JavaScript to find and manipulate user interface elements. +In [the lesson on using TypeScript to modify the DOM](/lessons/js-dom) we +discussed how to use TypeScript to find and manipulate user interface elements. Two examples of this are _toggle the state of an element each time we click it_ and _update a counter when a separate button is clicked_. In each of these cases we are modifying some `state` of the user interface when responding to some @@ -14,9 +14,31 @@ change. We could implement the case of _toggle the state of an element each time we click it_ as: -```javascript -document.querySelector('button').addEventListener('click', function (event) { - event.target.classList.toggle('enabled') +```typescript +import './style.css' + +const buttonElement = document.querySelector('button') + +if (buttonElement) { + buttonElement.addEventListener('click', function (event) { + const clickedElement = event.target as HTMLElement + + if (clickedElement) { + clickedElement.classList.toggle('enabled') + } + }) +} +``` + +We can use +[optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) +to shorten the code: + +```typescript +document.querySelector('button')?.addEventListener('click', function (event) { + const target = event.target as HTMLElement | null + + target?.classList.toggle('enabled') }) ``` @@ -26,14 +48,16 @@ the presence of the class to indicate the state. We could implement the case of _update a counter when a separate button is clicked_ as: -```javascript +```typescript let counter = 0 -document.querySelector('button').addEventListener('click', function (event) { +document.querySelector('button')?.addEventListener('click', function () { counter++ - const counterElement = document.querySelector('.counterElement') - counterElement.innerText = counter + const counterElement = document.querySelector('.counterElement') + if (counterElement) { + counterElement.innerText = `${counter}` + } }) ``` @@ -164,9 +188,15 @@ would only need to update the variables (_state_) in our application. ## What might this code look like? -```js +```typescript +interface Transaction { + account: string + amount: number + details: string +} + // Or maybe load these from a file or an API -const transactions = [] +const transactions: Transaction[] = [] function render() { const checking = transactions @@ -187,14 +217,26 @@ function render() { ` - document.querySelector('body').innerHTML = html - document.querySelector('button').addEventListener('click', function () { - // Make a new transaction and add it - const newTransaction = new Transaction({ amount: 50, account: 'Checking' }) - transactions.push(newTransaction) - - render() - }) + const body = document.querySelector('body') + if (body) { + body.innerHTML = html + } + + const button = document.querySelector('button') + + if (button) { + button.addEventListener('click', function () { + // Make a new transaction and add it + const newTransaction: Transaction = { + amount: 50, + account: 'Checking', + details: 'Payment for Work', + } + transactions.push(newTransaction) + + render() + }) + } } ``` diff --git a/lessons/js-ui-as-state/lecture.md b/lessons/js-ui-as-state/lecture.md index b08222da..d3283b39 100644 --- a/lessons/js-ui-as-state/lecture.md +++ b/lessons/js-ui-as-state/lecture.md @@ -27,17 +27,15 @@ Theme: Next, 1 ``` -```js +```typescript let counter = 0 -function main() { - document.querySelector('button').addEventListener('click', function (event) { - counter++ +document.querySelector('button').addEventListener('click', function (event) { + counter++ - const counterElement = document.querySelector('p') - counterElement.textContent = counter - }) -} + const counterElement = document.querySelector('p') + counterElement.textContent = counter +}) ``` --- @@ -54,12 +52,12 @@ In the example we are using a _local_ variable to track the _state_ of the count ## Empty HTML -```js +```typescript let counter = 0 function render() { const html = ` -

0

+

${counter}

` @@ -74,9 +72,7 @@ function render() { }) } -function main() { - render() -} +render() ``` --- @@ -93,12 +89,16 @@ _Every_ time we update `counter` we _repaint_ the entire user interface. # Scoreboard +Take contents of the `` + [HTML](https://raw.githubusercontent.com/suncoast-devs/scoreboard-template/master/index.html) [CSS](https://raw.githubusercontent.com/suncoast-devs/scoreboard-template/master/screen.css) --- -```js +```typescript +import './style.css' + function render() { const html = `

My Score Board

@@ -124,13 +124,15 @@ function render() { // Setup event listeners here } + +render() ``` --- # Setup state -```js +```typescript let teamOneName = 'Team 1' let teamOneScore = 0 @@ -148,12 +150,18 @@ Change static text such as: ```html

Team 1

+

0

+
``` to ```html

${teamOneName}

+

${teamOneScore}

+
+ +
``` --- @@ -168,50 +176,68 @@ to The event listening functions should update the appropriate variable and call `render` -````js -document.querySelector('.team1 .add').addEventListener('click', function (event) { +```typescript +document.querySelector('.team1 .add')?.addEventListener('click', function () { teamOneScore++ render() }) -document.querySelector('.team1 .subtract').addEventListener('click', function (event) { - teamOneScore-- - render() -}) -document.querySelector('.team1 input').addEventListener('input', function (event) { - teamOneName = event.target.value - render() -}) +document + .querySelector('.team1 .subtract') + ?.addEventListener('click', function () { + teamOneScore-- + render() + }) +document + .querySelector('.team1 input') + ?.addEventListener('input', function (event) { + const target = event.target as HTMLInputElement + + teamOneName = target?.value + render() + }) -document.querySelector('.team2 .add').addEventListener('click', function (event) { +document.querySelector('.team2 .add')?.addEventListener('click', function () { teamTwoScore++ render() }) -document.querySelector('.team2 .subtract').addEventListener('click', function (event) { - teamTwoScore-- - render() -}) -document.querySelector('.team2 input').addEventListener('input', function (event) { - teamTwoName = event.target.value - render() -}) +document + .querySelector('.team2 .subtract') + ?.addEventListener('click', function () { + teamTwoScore-- + render() + }) +document + .querySelector('.team2 input') + ?.addEventListener('input', function (event) { + const target = event.target as HTMLInputElement + + teamTwoName = target?.value + render() + }) ``` + --- [.column] ## Change state to be an object for each team -```js -const teamOne = { +```typescript +interface Team { + name: string + score: number +} + +const teamOne: Team = { name: 'Team 1', score: 0, } -const teamTwo = { +const teamTwo: Team = { name: 'Team 2', score: 0, } -```` +``` [.column] @@ -243,7 +269,7 @@ to # Render -```js +```typescript function renderTeam(team) { const html = `
@@ -268,24 +294,25 @@ function renderTeam(team) { # Listeners -```js -function setupListeners(team) { +```typescript +function setupListeners(team: Team) { document .querySelector(`.team${team.id} .add`) - .addEventListener('click', function (event) { + ?.addEventListener('click', function () { team.score++ render() }) document .querySelector(`.team${team.id} .subtract`) - .addEventListener('click', function (event) { + ?.addEventListener('click', function () { team.score-- render() }) document .querySelector(`.team${team.id} input`) - .addEventListener('input', function (event) { - team.name = event.target.value + ?.addEventListener('input', function (event) { + const target = event.target as HTMLInputElement + team.name = target?.value render() }) } @@ -293,6 +320,23 @@ function setupListeners(team) { --- +```typescript +function render() { + const html = ` +

My Score Board

+
+${renderTeam(teamOne)} +${renderTeam(teamTwo)} +
` + + document.body.innerHTML = html + setupListeners(teamOne) + setupListeners(teamTwo) +} +``` + +--- + # Using Arrays - Change state to be an array @@ -307,8 +351,8 @@ function setupListeners(team) { # State -```js -const teams = [ +```typescript +const teams: Team[] = [ { id: 1, name: 'Team 1', @@ -327,22 +371,24 @@ const teams = [ # Render -```js +```typescript function render() { const html = `

My Score Board

-${teams.map(function (team) { - return renderTeam(team) -}.join(''))} +${teams + .map(function (team: Team) { + return renderTeam(team) + }) + .join('')}
` document.body.innerHTML = html - teams.forEach(function (team) { + teams.forEach(function (team: Team) { setupListeners(team) }) } @@ -352,7 +398,7 @@ ${teams.map(function (team) { # Add more teams! -```js +```typescript { id: 3, name: 'Team 3', @@ -386,11 +432,8 @@ footer button { [.column] - -```js -document -.querySelector('button') -.addEventListener('click', function (event) { +```typescript +document.querySelector('button')?.addEventListener('click', function (event) { // Reset the teams teams = [ { id: 1, name: 'Team 1', score: 0 }, diff --git a/programs/web-development.yaml b/programs/web-development.yaml index 8024b5e4..9baf6b91 100644 --- a/programs/web-development.yaml +++ b/programs/web-development.yaml @@ -72,7 +72,6 @@ modules: - js-dom - js-event-loop - js-ui-as-state - - js-classes - js-modules - react-intro - react-assets diff --git a/web/static/lectures/js-modules-lecture.pdf b/web/static/lectures/js-modules-lecture.pdf index e08aa6776c7a82b6bfaad4d63b3f4484b6b264d2..63b3eb35f6a9f053807ebe8b89e877c64346cf0e 100644 GIT binary patch delta 84 zcmWm2u@QhE3xrGTR6+Ux+-DYA delta 84 zcmWN`u@QhE39DIUY fER4m~$|&Gz+Rf=-k{;8WWw(KJUr+2@g|(#*`N0<( diff --git a/web/static/lectures/js-ui-as-state-lecture.pdf b/web/static/lectures/js-ui-as-state-lecture.pdf index 1cf9c495a57d3e0c8800754a6041d27174a3f5d7..1ff771c7d56c3ab57f8a74269583696805d7bb3d 100644 GIT binary patch delta 83 zcmWN_u@QhU2mrvd%@mFRAwt-}B?y5#YdhO4;K=&ruWfCwoF>gIiAg<@1Z;9c927oT e{tysw8kMz?TIw+z8PRJ Date: Mon, 26 Jul 2021 18:47:14 -0400 Subject: [PATCH 10/13] Squashed commit of the following: commit 61f0cc66d3e333479ded48d799951e5e6414ebb6 Author: Gavin Stark Date: Wed Jul 14 22:04:03 2021 -0400 React state lectures into hooks commit 08c1b0fe664e9f160c7c5c49e1dccc74450cc4b1 Author: Gavin Stark Date: Wed Jul 14 14:06:08 2021 -0400 Converts react-intro to hooks --- lessons/react-intro/index.md | 481 +++++----- lessons/react-intro/lecture.md | 286 +++--- lessons/react-state-with-fetch/index.md | 573 +++++++++++ lessons/react-state-with-fetch/lecture.md | 397 ++++++++ lessons/react-state/index.md | 886 ++++-------------- lessons/react-state/lecture.md | 685 ++++---------- programs/web-development.yaml | 2 +- web/static/lectures/react-intro-lecture.pdf | Bin 131 -> 131 bytes web/static/lectures/react-state-lecture.pdf | Bin 131 -> 132 bytes .../react-state-with-fetch-lecture.pdf | Bin 0 -> 131 bytes 10 files changed, 1711 insertions(+), 1599 deletions(-) create mode 100644 lessons/react-state-with-fetch/index.md create mode 100644 lessons/react-state-with-fetch/lecture.md create mode 100644 web/static/lectures/react-state-with-fetch-lecture.pdf diff --git a/lessons/react-intro/index.md b/lessons/react-intro/index.md index f52b69a1..1ec89f6c 100644 --- a/lessons/react-intro/index.md +++ b/lessons/react-intro/index.md @@ -42,23 +42,29 @@ reduce code. ## Class versus Function components -We will begin by looking at `class` based React Components. In a future lesson, -we will be looking at `function` based components. These are the two current -ways to create React Components. `class` components were introduced first and -you will see most documentation relate to this style. However, the React team, -and the community as a whole, are migrating to `function` based components. SDG -will encourage you to use function components once we learn them. We'll start -with `class` components since they are where React started and where you'll find -the most documentation help. +These are the two current ways to create React Components. `class` components +were introduced first and you will see most documentation relate to this style. +However, the React team, and the community as a whole, are migrating to +`function` based components. In this course we will teach you the function +component style. If you continue with React in your career and work on projects +that use `class` components you won't have much trouble picking up that style. ## What does a Component look like The rules of a React component are: -- It must `extend React.Component` -- It must have a `render()` method that returns JSX -- The JSX that `render()` returns must consist of _exactly_ one main element, - with other elements contained within. We'll see this more later. +- It must be named following the `PascalCase` style. +- It must be a function (`function` style or arrow-style) that returns JSX +- The JSX returned must consist of _exactly_ one main element, with other + elements contained within. We'll see this more later. + +Example: + +```jsx +function HelloWorld() { + return

Hello, World!

+} +``` ## What is JSX? @@ -70,22 +76,37 @@ style. ## Simplest React Component ```javascript -class HelloWorld extends React.Component { - render() { - return
Hello, World!
- } +function HelloWorld() { + return

Hello, World!

} ``` When this component is presented on the page it will render -`
Hello, World
` +`

Hello, World

`. This done by a process known as `transpiling` or +converting JSX into real JavaScript code. When our code is sent to the browser +it is first manipulated by the JSX transpiling system. +`return

Hello, World!

` is turned into: + +```javascript +function HelloWorld() { + return React.createElement('p', null, 'Hello, World') +} +``` + +We _could_ code using the `React.createElement` style, but you would quickly +realize that it is very tedious and "hides" the structure of the component we +are trying to create. + +If you'd like to experiment with any of the other examples in the handbook, you +can visit: [Babel REPL](https://babeljs.io/repl) to see how JSX is transformed +into plain JavaScript. -## How does app-app help us use React Components? +## How do we use React Components? React Components can represent the **entire** web page, or be mixed in with static content of the page. -In the code we write with `app-app` we will use React to generate all of the +In the code we write in our projects we will use React to generate all of the content on the page via components. React is very powerful for this but it is good to know that you can also add a small component to an existing non-React project just as easily. @@ -103,8 +124,8 @@ parts) If we rendered this without JavaScript it would be an empty page. It is thus up to JavaScript to connect our React code to our HTML. -In our SDG template from `app-app` we include an `index.js` -- this script loads -React and a component we provide named `App` +In our template we include an `main.tsx` -- this script loads React and a +component we provide named `App` ```js import React from 'react' @@ -112,20 +133,25 @@ import ReactDOM from 'react-dom' import './index.css' import App from './App' -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render( + + + , + document.getElementById('root') +) ``` React connects an existing `DOM` element to a component with the `ReactDOM` method. Here we state that the element with the `id` of `root` will be replaced -with the component `App` that we `import` from the `App.jsx` file. +with the component `App` that we `import` from the `App.tsx` file. -Typically we will not have to adjust the `index.html` or the `index.js` files. -We will start writing our code in our `App.jsx` file. +Typically we will not have to adjust the `index.html` or the `main.tsx` files. +We will start writing our code in our `App.tsx` file. ## JSX Files You may have noticed that we define our React Components in files that end in -`.jsx` instead of `.js`. The `.jsx` extension allows our editors and our code +`.tsx` instead of `.ts`. The `.tsx` extension allows our editors and our code management tools to know we are using the `JSX` extensions. Browsers do not understand `JSX` by default so a [transpile](https://en.wikipedia.org/wiki/Source-to-source_compiler) step takes @@ -134,12 +160,7 @@ browser **can** understand. # Build a simple React application -Let's generate a new `app-app` project using the `gamma` stack that will set up -a new React project. - -```shell -app-app --gamma ReactArticles -``` +Let's generate a new project that will set up a new React project. This is some sample HTML we will work to create and learn how React Components can help simplify our code. @@ -177,40 +198,37 @@ Let's open up our main component, the `App` component, and put this static content on the page. ```javascript -import React, { Component } from 'react' - -class App extends Component { - render() { - return ( -
- ) - } +import React from 'react' + +function App() { + return ( +
+
+ + + +
+
+ ) } export default App @@ -295,7 +313,8 @@ Let's start that process. ## Creating a `NewsArticle` component. Let's create a new file in the `components` directory and name it -`NewsArticle.jsx` +`NewsArticle.tsx`. If you do not already have a `components` directory in your +project, you should make one and then create `NewsArticle.tsx` inside. We'll add this as the first line: @@ -309,21 +328,21 @@ This line tells the script we are going to use `React` and it activates the Next, we will make our component: ```javascript -export class NewsArticle extends React.Component { +export function NewsArticle() { // code here } ``` the `export` at the beginning of that line, tells JavaScript we wish to share -this class outside of the file. We'll use that fact in just a moment. +this class outside of the file. It also exports this function by `name` rather +than as the _default_ export. See the lesson on JavaScript modules for more on +the difference between the two. We'll use the export in just a moment. And we will ensure it has a `render()` method. ```javascript -export class NewsArticle extends React.Component { - render() { - return
Something
- } +export function NewsArticle() { + return
Something
} ``` @@ -331,21 +350,21 @@ Now let's see if we have a working component. ## Using our new `NewsArticle` from the `App` -Returning to our `App.jsx` we can bring in this component to use. At the top, +Returning to our `App.tsx` we can bring in this component to use. At the top, and at the end of the list of `import` we will add ```javascript import { NewsArticle } from './components/NewsArticle' ``` -This line tells JavaScript we wish to use the `NewsArticle` class in this code, -and that it can be found in the file `NewsArticle.jsx` in the `components` +This line tells JavaScript we wish to use the `NewsArticle` component in this +code, and that it can be found in the file `NewsArticle.tsx` in the `components` folder. Notice we do not add the extension. The `import` system is smart and can tell we mean the `jsx` version. If there were multiple files with the same extension we'd have to be more clear. -Finally, we can add this element to our `render()` method. Let's insert it right -inside the `
` +Finally, we can add this element to our `App`. Let's insert it right inside the +`
` ```html @@ -359,33 +378,27 @@ This means our `NewsArticle` component is rendering itself. ## Composition This is the idea of `composition` -- we are now defining a component, that has -it's own content, that we can embed into another component, in this case the -`App`. +it's own content, embedded into another component, in this case the `App`. ## Update the `NewsArticle` -Let's take one example of the news article we have and make it the `render` -method's JSX. +Let's take one example of the news article we have and make it the method's JSX. ```javascript import React from 'react' -export class NewsArticle extends React.Component { - render() { - return ( - - ) - } +export function NewsArticle() { + return ( + + ) } ``` @@ -396,19 +409,17 @@ Let's remove the other two hardcoded `
`s leaving only our `` ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' -class App extends Component { - render() { - return ( -
-
- -
-
- ) - } +function App() { + return ( +
+
+ +
+
+ ) } export default App @@ -418,22 +429,20 @@ We should only see one article listed. If we repeat the `` we can have as many of the `
` structure as we want. ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' -class App extends Component { - render() { - return ( -
-
- - - - -
-
- ) - } +function App() { + return ( +
+
+ + + + +
+
+ ) } export default App @@ -470,11 +479,11 @@ This is great, however, our `` still shows the hardcoded data. ## Using props in a component -When React places our component on the page and calls the `render()` method to -generate the elements, it makes the supplied `props` available in a variable -named `this.props` -- the `this.props` is an object whose keys are the names of -the properties. In our case this is `this.props.title` and `this.props.body` -- -the corresponding values are supplied as well. +When React places our component on the page and calls the method to generate the +elements, it makes the supplied `props` available in the argument to the +function. The `props` argument is an object whose keys are the names of the +properties. In our case this is `props.title` and `props.body` -- the +corresponding values are supplied as well. We can use these in our component by using an _interpolation_ method within JSX. This is much like string interpolation in plain JavaScript but the syntax is @@ -483,23 +492,21 @@ slightly different: ```javascript import React from 'react' -export class NewsArticle extends React.Component { - render() { - return ( - - ) - } +export function NewsArticle(props) { + return ( + + ) } ``` Now when each of these components is rendered on the page, the unique values for -`this.props` are available and we now have a component that is: +`props` are available and we now have a component that is: - **reusable** - **customizable** @@ -510,30 +517,28 @@ This is great, and we have an application that can render any number of articles we want. However, we still must manually code these in our main application. ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' -class App extends Component { - render() { - return ( -
-
- - - -
-
- ) - } +function App() { + return ( +
+
+ + + +
+
+ ) } export default App @@ -544,7 +549,7 @@ It would be nice to render this from the data. ## Importing JSON data Let's start by making a JSON file named `articles.json` in the directory along -with `App.jsx` -- In this file, we will describe, in JSON, an array of articles +with `App.tsx` -- In this file, we will describe, in JSON, an array of articles we want to render. We will also give each article an `id` as if it came from an API since that is likely the most common case for where this data will eventually come from. @@ -574,34 +579,32 @@ eventually come from. ] ``` -We will be using this in our `App.jsx` so let's import it! +We will be using this in our `App.tsx` so let's import it! ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - return ( -
-
- - - -
-
- ) - } +function App() { + return ( +
+
+ + + +
+
+ ) } export default App @@ -612,7 +615,7 @@ its contents available as the variable `articles`! No parsing required! This is because the environment comes with a **`loader`** for JSON files and it knows how to read and parse them for us! -Let's use that data to build up an array of `` components. +Let's use that data to build up an array of `` components. ## Transforming data into components @@ -620,35 +623,33 @@ Since we want one `` that is related to each element of the `articles` array, we will bring out our friend `map` ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - const newsArticlesFromData = articles.map(article => ( - - )) - - return ( -
-
- - - -
-
- ) - } +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
+ + + +
+
+ ) } export default App @@ -717,22 +718,20 @@ Since we now have an array of the `` we can simply place them where we want them in place of the hardcoded data. ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - const newsArticlesFromData = articles.map(article => ( - - )) - - return ( -
-
{newsArticlesFromData}
-
- ) - } +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
{newsArticlesFromData}
+
+ ) } export default App @@ -751,26 +750,24 @@ is important to know. > (i.e. that same key can be in another array-to-component map in another part > of the app, but must be unique for this array) -Well, now it is handy that we had that `id` attribute of our JSON objects! We -will us that `id` as out key! +Wasn't it fortunte that we ensured our JSON objects had that `id` attribute! We +will us that `id` as our key! ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - const newsArticlesFromData = articles.map(article => ( - - )) - - return ( -
-
{newsArticlesFromData}
-
- ) - } +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
{newsArticlesFromData}
+
+ ) } export default App diff --git a/lessons/react-intro/lecture.md b/lessons/react-intro/lecture.md index 842604a8..db4acf4e 100644 --- a/lessons/react-intro/lecture.md +++ b/lessons/react-intro/lecture.md @@ -46,13 +46,21 @@ It was developed by Facebook around 2011/2012 and was released as an [open sourc --- +# We will teach you hooks + +Following our guide of teaching you currently deployed technolody but with an eye on the future... + +... we are teaching you hooks. However, if you start a `class` based project you'll be able to pick that up. + +--- + # What does a Component look like ## The rules of a React component are: -- It must `extend React.Component` -- It must have a `render()` method that returns JSX -- The JSX that `render()` returns must consist of _exactly_ one main element, with other elements contained within. We'll see this more later. +- It must be named following the `PascalCase` style. +- It must be a function (`function` style or arrow-style) that returns JSX. +- The JSX returned must consist of _exactly_ one main element, with other elements contained within. We'll see this more later. --- @@ -65,10 +73,8 @@ JSX is an extension to JavaScript that allows us to an HTML-like syntax in our J # Simplest React Component ```javascript -class HelloWorld extends React.Component { - render() { - return
Hello, World!
- } +function HelloWorld() { + return

Hello, World!

} ``` @@ -77,30 +83,41 @@ class HelloWorld extends React.Component { # HTML in our JS?!? ```javascript -return
Hello, World!
+return

Hello, World!

``` --- -# How does app-app help us use React Components? +# Transpiling -- `app-app` for React is inspired by `create-react-app` which is the default tool for generating React projects. -- React Components can represent the **entire** web page, or be mixed in with static content of the page. -- `app-app` turns our whole page/app/site over to React +Transpiling turns one language/format to another. ---- +```javascript +return

Hello, World!

+``` -# Let's create a new app-app React project +Becomes: +```javascript +function HelloWorld() { + return React.createElement('p', null, 'Hello, World') +} ``` -app-app --gamma ReactArticles -``` + +Try the [Babel REPL](https://babeljs.io/repl) to see how JSX is transformed. + +--- + +# How do we use React Components? + +- React Components can represent the **entire** web page, or be mixed in with static content of the page. +- Our project will turn our whole page/app/site over to React --- # HTML file -The template SDG uses from `app-app` will generate an `index.html` file that looks like the following (only the `` is shown and only the relevant parts) +The template SDG uses will generate an `index.html` file that looks like the following (only the `` is shown and only the relevant parts) ```html @@ -118,7 +135,7 @@ If we rendered this without JavaScript it would be an empty page. It is thus up # User Interface all in JavaScript -In our SDG template from `app-app` we include an `index.js` -- this script loads React and a component we provide named `App` +In our SDG template we include an `main.tsx` -- this script loads React and a component we provide named `App` ```js import React from 'react' @@ -133,7 +150,7 @@ ReactDOM.render(, document.getElementById('root')) # ReactDOM is the glue between HTML and JavaScript -React connects an existing `DOM` element to a component with the `ReactDOM` method. Here we state that the element with the `id` of `root` will be replaced with the component `App` that we `import` from the `App.jsx` file. +React connects an existing `DOM` element to a component with the `ReactDOM` method. Here we state that the element with the `id` of `root` will be replaced with the component `App` that we `import` from the `App.tsx` file. Typically we will not have to adjust the `index.html` or the `index.js` files. We will start writing our code in our `App.jsx` file. @@ -141,11 +158,9 @@ Typically we will not have to adjust the `index.html` or the `index.js` files. W # JSX Files and _transpiling_ -You may have noticed that we define our React Components in files that end in `.jsx` instead of `.js`. - -The `.jsx` extension allows our editors and our code management tools to know we are using the `JSX` extensions. +You may have noticed that we define our React Components in files that end in `.tsx` instead of `.ts`. -Browsers do not understand `JSX` by default so a [transpile](https://en.wikipedia.org/wiki/Source-to-source_compiler) step takes place automatically. This step turns our `JSX` into plain `JavaScript` that a browser **can** understand. +The `.tsx` extension allows our editors and our code management tools to know we are using the `JSX` extensions. --- @@ -184,40 +199,37 @@ This is some sample HTML we will work to create and learn how React Components c We'll take our HTML and place it _ALL_ inside the `render` method of ou `App` ```javascript -import React, { Component } from 'react' - -class App extends Component { - render() { - return ( -
-
- - - -
-
- ) - } +import React from 'react' + +function App() { + return ( +
+
+ + + +
+
+ ) } export default App @@ -314,7 +326,7 @@ import React from 'react' # Next, we will make our component: ```javascript -export class NewsArticle extends React.Component { +export function NewsArticle() { // Code here } ``` @@ -323,15 +335,13 @@ The `export` at the beginning of that line, tells JavaScript we wish to share th --- -# Add a default render method +# Add a return For now, as a test, we'll have it just make a `
` with some text ```javascript -export class NewsArticle extends React.Component { - render() { - return
Something
- } +export function NewsArticle() { + return
Something
} ``` @@ -339,7 +349,7 @@ export class NewsArticle extends React.Component { # Prepare `App` to use `NewsArticle` -In `App.jsx` add: +In `App.tsx` add: ```javascript import { NewsArticle } from './components/NewsArticle' @@ -370,22 +380,18 @@ Let's take one example of the news article we have and make it the `render` meth ```javascript import React from 'react' -export class NewsArticle extends React.Component { - render() { - return ( - - ) - } +export function NewsArticle() { + return ( + + ) } ``` @@ -402,19 +408,17 @@ You should notice that our app now has **THREE** articles. The first comes from Let's remove the other two hardcoded `
`s leaving only our `` ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' -class App extends Component { - render() { - return ( -
-
- -
-
- ) - } +function App() { + return ( +
+
+ +
+
+ ) } ``` @@ -425,22 +429,20 @@ class App extends Component { We should only see one article listed. If we repeat the `` we can have as many of the `
` structure as we want. ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' -class App extends Component { - render() { - return ( -
-
- - - - -
-
- ) - } +function App() { + return ( +
+
+ + + + +
+
+ ) } export default App @@ -475,9 +477,9 @@ We can add properties to our components by specifying them in a very similar way # Using props in a component -All the properties added in the _USAGE_ of a component is present to us inside a component with `this.props` +All the properties added in the _USAGE_ of a component is present to us inside a component via an argument we will name `props` -So our `title` is in a variable named `this.props.title` and our `body` is in a variable called `this.props.body` +So our `title` is in a variable named `props.title` and our `body` is in a variable called `props.body` In the places where we have hard coded data we can replace with variables @@ -496,18 +498,16 @@ We can replace all the hardcoded text with values from `this.props` ```javascript import React from 'react' -export class NewsArticle extends React.Component { - render() { - return ( - - ) - } +export function NewsArticle() { + return ( + + ) } ``` @@ -610,22 +610,20 @@ const newsArticlesFromData = articles.map(article => ( Since we now have an array of the `` we can simply place them where we want them in place of the hardcoded data. ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - const newsArticlesFromData = articles.map(article => ( - - )) - - return ( -
-
{newsArticlesFromData}
-
- ) - } +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
{newsArticlesFromData}
+
+ ) } export default App @@ -648,22 +646,20 @@ There is one other thing we need to do. If you look in your console you will see --- ```javascript -import React, { Component } from 'react' +import React from 'react' import { NewsArticle } from './components/NewsArticle' import articles from './articles' -class App extends Component { - render() { - const newsArticlesFromData = articles.map(article => ( - - )) - - return ( -
-
{newsArticlesFromData}
-
- ) - } +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
{newsArticlesFromData}
+
+ ) } export default App diff --git a/lessons/react-state-with-fetch/index.md b/lessons/react-state-with-fetch/index.md new file mode 100644 index 00000000..259daf50 --- /dev/null +++ b/lessons/react-state-with-fetch/index.md @@ -0,0 +1,573 @@ +--- +title: React State With Fetch +--- + +# A more complex example: Tic Tac Toe With an API + +In [React State](/lessons/react-state) we saw how to use the `useState` hook to +manage data that is changing in response to a user event. We built a simple +component that counted the number of times we clicked on a button. Managing this +kind of data is known as `local state`. The other type of state is +`remote state` or `remote data` or `server state`. Let's extend our knowledge of +`state` by interacting with a remote API. + +The API we'll be using for this example is an +[unbeatable Tic Tac Toe API](https://sdg-tic-tac-toe-api.herokuapp.com/). Read +the API to get familiar with how it works. You'll notice that there are three +main endpoints: + +- Create a new game + +- Make a move in a game + +- Get the state of a game + +We'll be using those API endpoints during this example. + +# Revisiting our dynamic application workflow + +For this implementation we'll again revisit our five phases of building a +dynamic app. + +1. Static Implementation +2. Make a state object containing data +3. Try manually changing the value in the state +4. Connect actions (later on, we'll add API interaction here) +5. Update state + +## Step 1 - Static Implementation + +We'll begin by designing our Tic Tac Toe game. + +### Here is our JSX + +```jsx +export function App() { + return ( +
+

+ Tic Tac Toe - +

+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+
+ ) +} +``` + +### Here is some sample CSS + +```css +:root { + /* CSS Variables for all the font colors and sizes. Try changing these! */ + --header-background: #5661b3; + --header-text-color: #fff9c2; + --header-font-size: 2rem; + --square-font-size: calc(8 * var(--header-font-size)); + --square-text-color: #5661b3; + --square-background-color: #e6e8ff; + --square-border: 3px solid var(--square-text-color); + + font: 16px / 1 sans-serif; +} + +html { + height: 100%; +} + +body { + margin: 0; + min-height: 100%; +} + +h1 { + /* center the header */ + text-align: center; + + /* Use a sans serif font with a little spacing and color */ + font-family: Verdana, Geneva, Tahoma, sans-serif; + letter-spacing: 0.4rem; + font-size: var(--header-font-size); + color: var(--header-text-color); + + /* Remove margins and set a little padding */ + margin: 0; + padding: var(--header-font-size); + + /* Set a background color for the header */ + background-color: var(--header-background); +} + +ul, +li { + /* Be gone margins! */ + margin: 0; + padding: 0; + + /* and list styles */ + list-style: none; +} + +ul { + /* Make the height of the list equal to the height of the page MINUS the height taken by the header */ + height: calc(100vh - 3 * var(--header-font-size)); + + /* Display the list as a 3 column and three row grid */ + display: grid; + grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr; + + /* Add a little gap between to allow the background color through */ + gap: 1rem; + + /* Set the background color that will show through the gap */ + background-color: var(--square-text-color); +} + +ul li { + /* Use a monospace font */ + font-family: monospace; + font-size: var(--square-font-size); + + /* Style the background color of the item */ + background-color: var(--square-background-color); + + /* Make the cursor a pointer by default */ + cursor: pointer; + + /* Center the text in the LI */ + display: flex; + align-items: center; + justify-content: center; + transition: 1s font-size ease-in-out; +} + +ul li.taken { + cursor: not-allowed; +} + +ul li.small { + font-size: 4rem; +} + +ul li.not-allowed-click { + background-color: red; +} +``` + +### Try changing some of the `
  • ` entries + +This static implementation should allow us to put `X` and `O` elements in any of +the `
  • ` elements and see that the board renders correctly. + +Take a moment and change some of these entries and see that the UI shows what we +want. This is an important step as we want to validate that as we fill in our +state, and use it to populate the board, we can see a game of `X` and `O`. + +When done, make sure all the entries are blank again. + +## Step 2: Make a state using data + +When we are using an API we want to use a state with the same "shape" +(structure) as the API uses. Looking at the API response of a new game we'll see +it generates data like this: + +```json +{ + "winner": "X", + "id": 42, + "board": [ + [" ", " ", " "], + [" ", " ", " "], + [" ", " ", " "] + ] +} +``` + +We should use good default values for our initial state, so we'll make the +`winner` and `id` values equal to `null` to indicate we don't have any values. +We'll leave the `board' equal to the two-dimensional array of strings. + +```js +const [game, setGame] = useState({ + board: [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '], + ], + id: null, + winner: null, +}) +``` + +We can then update the static representation of our `
  • ` game board: + +```jsx +return ( +
    +

    + Tic Tac Toe - +

    +
      +
    • {game.board[0][0]}
    • +
    • {game.board[0][1]}
    • +
    • {game.board[0][2]}
    • +
    • {game.board[1][0]}
    • +
    • {game.board[1][1]}
    • +
    • {game.board[1][2]}
    • +
    • {game.board[2][0]}
    • +
    • {game.board[2][1]}
    • +
    • {game.board[2][2]}
    • +
    +
    +) +``` + +We'll use `board[0]` to represent the first **row** of squares, then +`board[0][0]` is the first square on that row, `board[0][1]` the second, and +`board[0][2]` the last. The same will be true of the remaining rows. + +## Step 3 - Try manually changing the state + +Now try replacing a few of the empty strings with some `X` and `O` values that +you might see in a real game of Tic Tac Toe. + +We should see the game board render with the appropriate values in the squares! + +## Step 4 - Connect the actions + +We will begin by defining a method that will handle clicking on a cell. + +In this case we'll need to know the row and column of the cell so we might write +our `handleClickCell` method like this: + +```js +function handleClickCell(row, column) { + console.log(`You clicked on row ${row} and column ${column}`) +} +``` + +Notice here we aren't using the typical function that takes an `event`. This is +because we need additional context to handle clicking. We'll deal with this by +writing a slightly different `onClick` method for each of the `
  • ` + +```jsx +
  • handleClickCell(0, 0)}>{game.board[0][0]}
  • +``` + +In this case, the value of the `onClick` is itself an arrow function! However, +we have placed it **inline**. By doing this, we can specify the row and column +values. + +The way to think about `onClick={() => handleClickCell(0, 0)}` is this: + +> When you click on this specific `li`, call the function +> `() => handleClickCell(0,0)` -- When that function is called **it** will call +> `handleClickCell` and specify `0` as the `row` and `0` as the `column`. + +So we might do the same with the remaining `li` + +```jsx +
      +
    • handleClickCell(0, 0)}>{game.board[0][0]}
    • +
    • handleClickCell(0, 1)}>{game.board[0][1]}
    • +
    • handleClickCell(0, 2)}>{game.board[0][2]}
    • +
    • handleClickCell(1, 0)}>{game.board[1][0]}
    • +
    • handleClickCell(1, 1)}>{game.board[1][1]}
    • +
    • handleClickCell(1, 2)}>{game.board[1][2]}
    • +
    • handleClickCell(2, 0)}>{game.board[2][0]}
    • +
    • handleClickCell(2, 1)}>{game.board[2][1]}
    • +
    • handleClickCell(2, 2)}>{game.board[2][2]}
    • +
    +``` + +Try clicking on each of the cells on the board, and we should see messages in +our developer console that matches up with the row and column we have been +clicking! + +## Step 5 - Update the state + +For this, we will use the Tic Tac Toe API. Reading the API, it appears we need +to "Create a new game" to get a "Game ID" so that we can register moves. + +We'll make a small change to our UI to add a button: + +```jsx +

    + Tic Tac Toe - +

    +``` + +And we will define a function `handleNewGame` that uses the API: + +```js +async function handleNewGame() { + // Make a POST request to ask for a new game + const response = await fetch( + 'https://sdg-tic-tac-toe-api.herokuapp.com/game', + { + method: 'POST', + headers: { 'content-type': 'application/json' }, + } + ) + + if (response.status === 201) { + // Get the response as JSON + const newGame = await response.json() + + // Make that the new state! + setGame(newGame) + } +} +``` + +This function uses the API to send a request to make a new game. Because we +designed our state to **exactly** match what the API returns, all we need to do +is take the `JSON` object that it returns and place it in the state. + +```js +// Get the response as JSON +const newGame = await response.json() + +// Make that the new state! +setGame(newGame) +``` + +Try this a few times in the UI. Use the `React Developer Tools` to look at the +`state` of our component after clicking the new game button. You should see an +empty board but with a new `id` value each time! + +You could also try making the initial state something other than a blank board. +You would see that making a new game will _reset_ it. **Don't forget to put the +default state back to an array of empty strings!** + +The `id` within the `state` will help us when we need to record the game actions +on a click. + +### Update handleClickCell + +When we click a cell, we need to build an API request to send to the server. The +response we get back, as we did with `handleNewGame`, will be in exactly the +form to use with `setGame`. + +That code looks like: + +```js +async function handleClickCell(row, column) { + // Generate the URL we need + const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${game.id}` + + // Make an object to send as JSON + const body = { row: row, column: column } + + // Make a POST request to make a move + const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }) + + if (response.status === 201) { + // Get the response as JSON + const newGame = await response.json() + + // Make that the new state! + setGame(newGame) + } +} +``` + +Other than sending the url, sending a `body` containing the `row` and `column` +information, the structure of this code is very similar to `handleNewGame`. This +includes the processing of the response: + +```js +// Get the response as JSON +const newGame = await response.json() + +// Make that the new state! +setGame(newGame) +``` + +So as we make a move, we should see the API send us back a game state. + +This game state will have our recorded move, but it will also have **the +computer's move as well** + +Make a new game and try a few moves! + +### Handle the winner + +The API will also tell us the winner of the game. We can make the header display +the winner information. + +To do this, we'll first extract the static data to a variable. + +```jsx + const header = 'Tic Tac Toe' + + return ( +
    +

    + {header} - +

    +``` + +Now we can make this string dynamic by using a +[ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) +operator. + +```js +const header = game.winner ? `${game.winner} is the winner` : 'Tic Tac Toe' +``` + +And with this, we have a playable Tic Tac Toe game that demonstrates how to use +an API and React State to make an interactive app! + +## Improve the code + +We can improve the code to remove some duplication in the drawing of the game +board. + +We can use `map` to generate the elements of the board. In this case, since we +have an _array of arrays_ we'll have to use **two** `map` calls. The outer one +will loop through the `rows` and the inner one will loop through the `columns` + +```jsx +
      + {game.board.map((boardRow, rowIndex) => { + return boardRow.map((cell, columnIndex) => { + return ( +
    • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
    • + ) + }) + })} +
    +``` + +Two dimensional arrays can be tricky at first so study this code. Maybe some +`console.log` will help make the code more clear: + +```jsx +
      + {game.board.map((boardRow, rowIndex) => { + console.log(`The rowIndex is ${rowIndex} and the boardRow is ${boardRow}`) + return boardRow.map((cell, columnIndex) => { + console.log( + `-- With the inside loop the columnIndex is ${columnIndex} and the cell is ${cell}` + ) + return ( +
    • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
    • + ) + }) + })} +
    +``` + +> **IMPORTANT** -- Any time we generate JSX dynamically such as with a `map` we +> need to include a `key` value for the outer-most element. In this case we need +> a unique value for the `
  • `. The value only needs to be unique to it's +> siblings. So in this case the `columnIndex` is enough to tell React "this is +> the 0th element ... this is the 1st element ... this is the 2nd element" and +> React will be satisfied. + +### Blocking clicks + +You may have noticed that if you try to click on a game square before there is a +game created, or after a winner exists, we'll get back some error information +from the API. + +Let's block clicks in these cases: + +- There is no game created + +- The user clicks on an occupied cell + +- Someone has won + +We can do this by introducing the concept of a +[guard clause](). A +`guard clause` is a boolean conditional (typically an `if`) statement that +checks for conditions under which we don't want the rest of the function/method +to execute. Typically inside a `guard clause if statement`, we would see a +`return` statement, which would end the function's execution. + +In our case we want to add this code to the top of our `handleClickCell` +function: + +```js +if ( + // No game id + game.id === undefined || + // A winner exists + game.winner || + // The space isn't blank + game.board[row][column] !== ' ' +) { + return +} +``` + +This allows us to block the click for each of the conditions we want to prevent. + +If you look in the `CSS` file, you'll see that we have some styling for cells +that show any cell with a class of `taken` to have a cursor that indicates we +cannot click. This adds a nice visual effect to align with the +`guard clause`protection we just added. + +We can dynamically set the class name of an `li` again using a `ternary` +expression: + +```jsx +
  • handleClickCell(rowIndex, columnIndex)} +> + {cell} +
  • +``` + +This code will set the `className` to a blank string if the cell is still open +(equal to space) and to `taken` if there is any other value (say an `X` or an +`O`) + +## Reviewing the Steps for an API based component + +Our steps for creating a dynamic user interface have updated slightly. Let's +take a moment and re-review the list, augmenting it with new steps and detailing +existing ones based on what we've learned about managing state. + +- Step 1 - Static implementation + +- Step 2 - Make a state object containing data + +- Step 3 - Try manually changing the value in the state. + +- Step 4 - Connect actions + +- Step 5 - Update state + +- Step 5a - Use fetch to send required data to the API +- Step 5b - Use the response from fetch to get the new state +- Step 5c - Update the state + +- Step 6 - Refine dynamic nature of UI based on state data diff --git a/lessons/react-state-with-fetch/lecture.md b/lessons/react-state-with-fetch/lecture.md new file mode 100644 index 00000000..c6338f45 --- /dev/null +++ b/lessons/react-state-with-fetch/lecture.md @@ -0,0 +1,397 @@ +Theme: Next, 1 + +# React State With Fetch + +--- + +# A more complex example: Tic Tac Toe With an API + +--- + +# Step 1 - Static Implementation + +```jsx +function App() { + return ( +
    +

    + Tic Tac Toe - +

    +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    +
    + ) +} +``` + +--- + +# Step 2: Make a state using data + +[.column] + +- When using an API, taking the data example from the API is a great way to start. + +[.column] + +```js +{ + "board": [ + [ + " ", + " ", + " " + ], + [ + " ", + " ", + " " + ], + [ + " ", + " ", + " " + ] + ], + "winner": null, +} +``` + +--- + +# Step 2 Continued: + +[.column] + +```jsx +function App() { + const [game, setGame] = useState({ + board: [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '], + ], + id: null, + winner: null, + }) + + return ( +
    +

    + Tic Tac Toe - +

    +
      +
    • {game.board[0][0]}
    • +
    • {game.board[0][1]}
    • +
    • {game.board[0][2]}
    • +
    • {game.board[1][0]}
    • +
    • {game.board[1][1]}
    • +
    • {game.board[1][2]}
    • +
    • {game.board[2][0]}
    • +
    • {game.board[2][1]}
    • +
    • {game.board[2][2]}
    • +
    +
    + ) +} +``` + +--- + +# Step 3: Try manually changing the state + +[.column] + +```jsx +const [game, setGame] = useState({ + board: [ + [' ', ' ', 'O'], + [' ', ' ', ' '], + ['X', ' ', ' '], + ], + id: null, + winner: null, +}) +``` + +--- + +# See that this affects the user interface + +--- + +# Step 4: Connect the actions + +- Define a method that will handle clicking on the cell +- We will need to know the row and column + +```js +function handleClickCell(row, column) { + console.log(`You clicked on row ${row} and column ${column}`) +} +``` + +--- + +```jsx +function App() { + const [game, setGame] = useState({ + board: [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '], + ], + id: null, + winner: null, + }) + + function handleClickCell(row, column) { + console.log(`You clicked on row ${row} and column ${column}`) + } + + return ( +
    +

    + Tic Tac Toe - +

    +
      +
    • handleClickCell(0, 0)}>{game.board[0][0]}
    • +
    • handleClickCell(0, 1)}>{game.board[0][1]}
    • +
    • handleClickCell(0, 2)}>{game.board[0][2]}
    • +
    • handleClickCell(1, 0)}>{game.board[1][0]}
    • +
    • handleClickCell(1, 1)}>{game.board[1][1]}
    • +
    • handleClickCell(1, 2)}>{game.board[1][2]}
    • +
    • handleClickCell(2, 0)}>{game.board[2][0]}
    • +
    • handleClickCell(2, 1)}>{game.board[2][1]}
    • +
    • handleClickCell(2, 2)}>{game.board[2][2]}
    • +
    +
    + ) +} +``` + +--- + +# Step 5: Update the state + +- For this, we will use the Tic Tac Toe API +- The first step is to make a new game by clicking on `new` + +--- + +```js +async function handleNewGame() { + // Make a POST request to ask for a new game + const response = await fetch( + 'https://sdg-tic-tac-toe-api.herokuapp.com/game', + { + method: 'POST', + headers: { 'content-type': 'application/json' }, + } + ) + + if (response.status === 201) { + // Get the response as JSON + const newGame = await response.json() + + // Make that the new state! + setGame(newGame) + } +} +``` + +```jsx +

    + Tic Tac Toe - +

    +``` + +--- + +# See that this updates the user interface + +- Test this by making the elements in the initial `board` state contain something other than spaces! +- Creating a new game should visually "reset" the board + +--- + +[.column] + +# Notice the state has some extra information in it now + +- We know the _id_ of the game. Useful for future API requests. + +[.column] + +```js +{ + "id": 5, + "board": [ + [ + " ", + " ", + " " + ], + [ + " ", + " ", + " " + ], + [ + " ", + " ", + " " + ] + ], + "winner": null, + "created_at": "2021-02-19T00:52:49.678Z", + "updated_at": "2021-02-19T00:52:49.678Z" +} +``` + +--- + +# Update handleClickCell + +```jsx +async function handleClickCell(row, column) { + // Generate the URL we need + const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${id}` + + // Make an object to send as JSON + const body = { row: row, column: column } + + // Make a POST request to make a move + const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }) + + if (response.status === 201) { + // Get the response as JSON + const newGame = await response.json() + + // Make that the new state! + setGame(game) + } +} +``` + +--- + +# Handle the winner + +- The API gives us information about the winner. +- Let us make the header display the winner + +--- + +# [fit] Dynamically generate the header + +```jsx +const header = 'Tic Tac Toe' + +return ( +
    +

    + {header} - +

    +``` + +--- + +# [fit] Now make it depend on the winner state + +```js +const header = game.winner ? `${game.winner} is the winner` : 'Tic Tac Toe' +``` + +--- + +# Remove duplication in the creation of the game board + +- Let us use `map` instead of repeating all the `li` + +```jsx +
      + {game.board.map((boardRow, rowIndex) => { + return boardRow.map((cell, columnIndex) => { + return ( +
    • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
    • + ) + }) + })} +
    +``` + +--- + +# Block clicks + +- When there is no game +- Or when the user clicks on an occupied cell +- Or when someone has won + +```js +if ( + // No game id + game.id === undefined || + // A winner exists + game.winner || + // The space isn't blank + game.board[row][column] !== ' ' +) { + return +} +``` + +--- + +# Dynamically set the class name + +- If the cell is not empty, set the class to `taken` + +```jsx +
  • handleClickCell(rowIndex, columnIndex)} +> + {cell} +
  • +``` + +--- + +# Steps: + +- Step 1 - Static implementation + +- Step 2 - Make a state object containing data + +- Step 3 - Try manually changing the value in the state. + +- Step 4 - Connect actions + +- Step 5 - Update state + +- Step 5a - Use fetch to send required data to the API +- Step 5b - Use the response from fetch to get the new state +- Step 5c - Update the state + +- Step 6 - Refine dynamic nature of UI based on state data diff --git a/lessons/react-state/index.md b/lessons/react-state/index.md index a1f4162f..4633d29b 100644 --- a/lessons/react-state/index.md +++ b/lessons/react-state/index.md @@ -1,5 +1,5 @@ --- -title: React State +title: React State and Introduction to Events --- See the lecture slides as this reading is under construction. @@ -26,7 +26,7 @@ them. That is, `NewsArticle` cannot change the value of the property. > `props` are passed from the **parent** to the **child** -> `props` are accessible via a `this.props` object in `class` based components +> `props` are accessible via a `props` argument What are we to do if we want to change data? What approach does React provide for initializing, storing, and changing data that varies during the time a @@ -34,21 +34,24 @@ component is visible on the page? # Enter `state` -Borrowing from our existing terminology of `objects`, React implements a system -called `state` to allow us to modify data during the lifetime of a component. +React implements a system called `state` to allow us to modify data during the +lifetime of a component. This is similar in concept to the idea of `state` in +object oriented systems. -`state` is a specifically named variable that is part of the `class`, much like -`this.props`. In this case the variable is named `this.state`. +In a functional component we use a system called `hooks` to implement features +such as tracking `state`-ful information. The name `hook` comes from the idea +that we are `hooking` into React's processing. -`state` can be modified. However, we must modify this variable in a very -specific way so that React knows we have changed the information. +We will start with the simplest `hook` in React, `useState`. -> `state` is a specifically named variable as part of the class (`this.state`) +`useState` is a React function that allows us to create a variable in our +component that can change over time. It comes from the standard React library +and follows the standard hook rules which are: -> `state` can be modified (but we need to use a specific method to make changes) - -> Changing `state` causes React to re-draw our component with the new state -> information. +1. Hooks should all begin with the word `use` and follow `camelCase` names. +1. Hooks must be called in the same order each time a compontent renders. The + easiest way to guarantee this is to not place a `useXXXX` hook inside of a + conditional, or have any "guard clauses" **before** the use of a hook method. # State changes lead to re-rendering @@ -102,85 +105,124 @@ several valuable advantages: Here is the static implementation of our click counter: ```js -export class Counter extends React.Component { - render() { - return ( -
    -

    The count is 0

    - -
    - ) - } +export function Counter() { + return ( +
    +

    The count is 0

    + +
    + ) } ``` -> NOTE: We'd have some CSS with this as well to give the counter a nice user -> experience. +## Step 2 - Add state hooks -# Step 2 - Make a state object containing data +We will add our first hook, known as `useState`. Here is the code to create the +state variables and display their value. We'll then break down this code +line-by-line -Now that we have a static design, we can review the implementation to find all -the elements that need to become dynamic. In our case, this is simply one -element, the current `count` of clicks. In more complex cases, this may be an -object with many properties, an array of simple data like strings or numbers, or -an array of objects. Whatever the structure of this state, we'll create an -**initial state** value and then use that value wherever we had static data. - -Here is what this looks like for our `Counter` component. We'll review the code -changes below. +```jsx +function Counter() { + // prettier-ignore + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) -```js -export class Counter extends React.Component { - state = { - count: 0, - } + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` -The first change to notice is: +Whoa! Let us break this down. -```js -state = { - count: 0, -} +We start the very first line of code with: + +```jsx +const counterValueAndSetMethod = useState(0) +``` + +This line of code does a few things. First, it declares that we are going to use +some state. It then says that the state's initial value is going to be the +number `0`. + +### `useState` rules + +`useState` has a few particular _rules_ that we need to remember: + +1. The value given to `useState` in parenthesis is used as the initial value + only the first time the component's instance is rendered. Even if the + component is rendered again due to a state change, the state's value isn't + reset to the initial value. This behavior may seem strange if we are going to + call `useState` again when that render happens. How React makes this happen + is a concept deeper than we have time to discuss here. + +2. `useState` always returns an _array_ with exactly _two_ elements. The + **first** element is the _current value of the state_ and the **second** + element is _a function that can change the value of this state_ + +### Using the `useState` return value + +Here are the next two lines of code: + +```jsx +const counter = counterValueAndSetMethod[0] +const setCounter = counterValueAndSetMethod[1] ``` -This code initializes an **instance** variable named `state` with a value being -an object containing a single key (`count`) with the value `0`. +These lines of code make two local variables to store the **current value** of +our state, which we call `counter` and the **method that updates the counter** +as `setCounter` -The name `state` here is a **very** essential fact. We cannot choose any name we -want for the `state` variable. The value we assign to the variable is entirely -up to us. We could have simply made the `state` value the `0` directly. However, -it is a fairly common practice to make this value an object with a key that -gives a _name_ to our data. +Then in the JSX, we can use those two local variables. The code +`

    The counter is {counter}

    ` will show the current value of the counter. +The code `` will +call `setCounter` to change the value of the counter, and make it the counter +plus one. -You'll see in later examples when we start working with APIs, the _shape_ of the -`state` object will follow the structure of the API data. Since we aren't using -an API here, we can control the design of the `state` variable. +However, this code is not as compact as we can make it! We can use +`array destructuring assignment` to simplify the code. -The next change is in our `p` paragraph. Instead of the static value `0` we -place `{this.state.count}`. This will render the value associated with the -`count` key inside the `this.state` variable (object). +The code: -If we were to add this component to a project, we would see that the component -would display `The count is 0` when it first renders. +```jsx +const counterValueAndSetMethod = useState(0) -> NOTE: A vitally important thing to remember is that your component will render -> the **first** time with whatever value is in the `this.state` variable. This -> means we need to _always_ provide an initial value for the `state` that _makes -> sense_. If we forgot to initialize `state` in this code, we would have -> received an **error** the first time our component rendered. +const counter = counterValueAndSetMethod[0] +const setCounter = counterValueAndSetMethod[1] +``` -> **Always provide a working default/initial value for `state`** +can be rewritten as such: + +```jsx +const [counter, setCounter] = useState(0) +``` + +and is how every example of `useState` will appear. See +[this article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) +for more details on how and why this syntax works. + +Thus our component will look like this: + +```jsx +function Counter() { + const [counter, setCounter] = useState(0) + + return ( +
    +

    The counter is {counter}

    + +
    + ) +} +``` + +We have just combined the best of both worlds. We have the simplicity of the +function component with the ability to update state! # Step 3 - Try manually changing the value in the state @@ -194,9 +236,7 @@ could see that the UI would reflect the new value. Change the state initializing code to: ```js -state = { - count: 42, -} +const [counter, setCounter] = useState(42) ``` and you will see the display update to `The count is 42`. @@ -225,21 +265,11 @@ have the counter update. In non-React-based JavaScript, we would set up an `addEventListener` for such an interaction. We would pass this function as an event handling function. -In React, the event handling function is still proper. In this case, we will use -the `arrow function` syntax. This ensures that the `this.state` is correctly set -when we need it. - -It is essential to use `arrow syntax` for your event handling functions based on -how **binding** works. To read more about binding `this` in JavaScript, see -[this article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). -Understanding `this` and `binding` is an essential concept for JavaScript -developers (and it is often an interview question) but outside the immediate -scope here. - -> **IMPORTANT** -- use `arrow functions` for your event handlers. +In React, the event handling function is still proper. However, we will connect +it to the event in a different way. ```js -handleClickButton = event => { +function handleClickButton(event) { event.preventDefault() console.log('Clicked!') @@ -286,10 +316,10 @@ that function each time it is clicked. ``` -In React we will use `onXXXXX` methods (e.g. `onClick`, `onSubmit`, -`onContextMenu`, `onChange`, etc.) when we want to associate an element to an -event handling function. In this case, we are telling React to call our -`handleClickButton` function each time the button is clicked. +In React we will use `onXXXXX` or `handleXXXXX` named methods (e.g. `onClick`, +`onSubmit`, `onContextMenu`, `onChange`, `handleClick`, etc.) when we want to +associate an element to an event handling function. In this case, we are telling +React to call our `handleClickButton` function each time the button is clicked. Now we know that we can connect a method to an event handling function. @@ -303,118 +333,46 @@ For our button, we want to: - Update the state to make the count equal to the incremented value -We will update this _algorithm_ to be specifically related to React: - -- Get the current count -- Increment the count -- **Make a new state** -- Tell React about the new state - That code looks like this: ```js -// Get the current count -const currentCount = this.state.count - // Increment -const newCount = currentCount + 1 +const newCount = count + 1 -// Make a new state -const newState = { - count: newCount, -} - -// Tell React about the new state -this.setState(newState) +// Tell React there is a new value for the count +setCount(newCount) ``` -All of this code should feel familiar except for the last line, -`this.setState(newState)`. This is the **special method** we use to tell React -about a new state. Typically we might write something like -`this.state.count = newCount` and while that line of code is syntactically -correct and won't generate any errors when we execute it, we will see that -`this.state.count = newCount` isn't enough. - -`this.setState` is a function given to us by the fact that we -`extends React.Component` and is used to tell React that **after** this function -is done, it should recognize this new state object and ** re-render** our -component. - -> NOTE: `this.setState` is the only way we should ever update state in a `class` -> component. - -> NOTE: After calling `this.setState` you will see that `this.state.count` has -> **NOT** been updated. the value of `this.state` isn't changed until React gets -> a chance to update state **AFTER** our `handleClickButton` method is done. -> This often confuses new React developers. - -# Our code so far: - -```jsx -export class Counter extends React.Component { - state = { - count: 42, - } - - handleClickButton = event => { - event.preventDefault() +> NOTE: After calling `setCount` you will see that `count` has **NOT** been +> updated. the value of `count` isn't changed until React gets a chance to +> update state **AFTER** our `handleClickButton` method is done. This often +> confuses new React developers. - // Get the current count - const currentCount = this.state.count - - // Increment - const newCount = currentCount + 1 - - // Make a new state - const newState = { - count: newCount, - } - - // Tell React about the new state - this.setState(newState) - } - - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` - -# Simplify the code - -There are some simplifications we could make to `handleClickButton`. - -The first is to make the creation of the `newState` a single statement: +We can simplify this code when we place it in our function: ```js -handleClickButton = event => { +function handleClickButton(event) { event.preventDefault() - const newState = { - count: this.state.count + 1, - } - - this.setState(newState) + setCount(count + 1) } ``` -We could continue and use a rule: - -> RULE: If we create a variable and use it only once, we _could_ replace the use -> of the variable with the variable's definition. +# Our code so far: -Using this rule: +```jsx +function CounterWithName() { + const [counter, setCounter] = useState(0) -```js -handleClickButton = event => { - event.preventDefault() + function handleButtonClick() { + setCounter(counter + 1) + } - this.setState({ count: this.state.count + 1 }) + return ( +
    + +
    + ) } ``` @@ -430,546 +388,48 @@ handleClickButton = event => { - Step 5 - Update state -# A more complex example: Tic Tac Toe With an API - -Let's extend our knowledge of `state` by interacting with an API. - -The API we'll be using for this example is an -[unbeatable Tic Tac Toe API](https://sdg-tic-tac-toe-api.herokuapp.com/). Read -the API to get familiar with how it works. You'll notice that there are three -main endpoints: - -- Create a new game - -- Make a move in a game - -- Get the state of a game - -We'll be using those API endpoints during this example. - -# Step 1 - Static Implementation - -We'll begin by designing our Tic Tac Toe game. - -```jsx -export class App extends Component { - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    -
    - ) - } -} -``` - -```css -:root { - /* CSS Variables for all the font colors and sizes. Try changing these! */ - --header-background: #5661b3; - --header-text-color: #fff9c2; - --header-font-size: 2rem; - --square-font-size: calc(8 * var(--header-font-size)); - --square-text-color: #5661b3; - --square-background-color: #e6e8ff; - --square-border: 3px solid var(--square-text-color); - - font: 16px / 1 sans-serif; -} - -html { - height: 100%; -} - -body { - margin: 0; - min-height: 100%; -} - -h1 { - /* center the header */ - text-align: center; - - /* Use a sans serif font with a little spacing and color */ - font-family: Verdana, Geneva, Tahoma, sans-serif; - letter-spacing: 0.4rem; - font-size: var(--header-font-size); - color: var(--header-text-color); - - /* Remove margins and set a little padding */ - margin: 0; - padding: var(--header-font-size); - - /* Set a background color for the header */ - background-color: var(--header-background); -} - -ul, -li { - /* Be gone margins! */ - margin: 0; - padding: 0; - - /* and list styles */ - list-style: none; -} - -ul { - /* Make the height of the list equal to the height of the page MINUS the height taken by the header */ - height: calc(100vh - 3 * var(--header-font-size)); - - /* Display the list as a 3 column and three row grid */ - display: grid; - grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr; - - /* Add a little gap between to allow the background color through */ - gap: 1rem; - - /* Set the background color that will show through the gap */ - background-color: var(--square-text-color); -} - -ul li { - /* Use a monospace font */ - font-family: monospace; - font-size: var(--square-font-size); - - /* Style the background color of the item */ - background-color: var(--square-background-color); - - /* Make the cursor a pointer by default */ - cursor: pointer; - - /* Center the text in the LI */ - display: flex; - align-items: center; - justify-content: center; - transition: 1s font-size ease-in-out; -} - -ul li.taken { - cursor: not-allowed; -} - -ul li.small { - font-size: 4rem; -} - -ul li.not-allowed-click { - background-color: red; -} -``` - -This static implementation should allow us to put `X` and `O` elements in any of -the `
  • ` elements and see that the board renders correctly. This is an -important step as we want to validate that as we fill in our state, and use it -to populate the board, we can see a game of `X` and `O`. - -# Step 2: Make a state using data - -When we are using an API we want to use a state with the same "shape" -(structure) as the API uses. Looking at the API response of a new game we'll see -it generates data like this: - -```json -{ - "winner": "X", - "id": 42, - "board": [ - [" ", " ", " "], - [" ", " ", " "], - [" ", " ", " "] - ] -} -``` - -We should use good default values for our initial state, so we'll make the -`winner` and `id` values equal to `null` to indicate we don't have any values. -We'll leave the `board' equal to the two-dimensional array of strings. - -```js -state = { - board: [ - [' ', ' ', ' '], - [' ', ' ', ' '], - [' ', ' ', ' '], - ], - id: null, - winner: null, -} -``` - -We can then update the static representation of our `
  • ` game board: - -```jsx -render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • {this.state.board[0][0]}
    • -
    • {this.state.board[0][1]}
    • -
    • {this.state.board[0][2]}
    • -
    • {this.state.board[1][0]}
    • -
    • {this.state.board[1][1]}
    • -
    • {this.state.board[1][2]}
    • -
    • {this.state.board[2][0]}
    • -
    • {this.state.board[2][1]}
    • -
    • {this.state.board[2][2]}
    • -
    -
    - ) -} -``` - -We'll use `board[0]` to represent the first **row** of squares, then -`board[0][0]` is the first square on that row, `board[0][1]` the second, and -`board[0][2]` the last. The same will be true of the remaining rows. - -# Step 3 - Try manually changing the state +### Adding more state -Now try replacing a few of the empty strings with some `X` and `O` values that -you might see in a real game of Tic Tac Toe. +Let us say we also wanted to keep track of a person's name on the counter. -We should see the game board render with the appropriate values in the squares! +With `hooks`, we will make two **independent** states that each track a single +piece of information. -# Step 4 - Connect the actions +Separating these pieces of state has a few benefits: -We will begin by defining a method that will handle clicking on a cell. +1. It is easier to remove one part of the state since it has its own variable + and state changing function. -In this case we'll need to know the row and column of the cell so we might write -our `handleClickCell` method like this: - -```js -handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) -} -``` - -Notice here we aren't using the typical function that takes an `event`. This is -because we need additional context to handle clicking. We'll deal with this by -writing a slightly different `onClick` method for each of the `
  • ` +2. We can more easily tell where in the code a piece of state or a state + changing function is used. ```jsx -
  • this.handleClickCell(0, 0)}>{this.state.board[0][0]}
  • -``` - -In this case, the value of the `onClick` is itself an arrow function! However, -we have placed it **inline**. By doing this, we can specify the row and column -values. - -The way to think about `onClick={() => this.handleClickCell(0, 0)}` is this: +function CounterWithName() { + const [counter, setCounter] = useState(0) + const [name, setName] = useState('Susan') -> When you click on this specific `li`, call the function -> `() => this.handleClickCell(0,0)` -- When that function is called **it** will -> call `handleClickCell` and specify `0` as the `row` and `0` as the `column`. - -So we might do the same with the remaining `li` - -```jsx -
      -
    • this.handleClickCell(0, 0)}>{this.state.board[0][0]}
    • -
    • this.handleClickCell(0, 1)}>{this.state.board[0][1]}
    • -
    • this.handleClickCell(0, 2)}>{this.state.board[0][2]}
    • -
    • this.handleClickCell(1, 0)}>{this.state.board[1][0]}
    • -
    • this.handleClickCell(1, 1)}>{this.state.board[1][1]}
    • -
    • this.handleClickCell(1, 2)}>{this.state.board[1][2]}
    • -
    • this.handleClickCell(2, 0)}>{this.state.board[2][0]}
    • -
    • this.handleClickCell(2, 1)}>{this.state.board[2][1]}
    • -
    • this.handleClickCell(2, 2)}>{this.state.board[2][2]}
    • -
    -``` - -Try clicking on each of the cells on the board, and we should see messages in -our developer console that matches up with the row and column we have been -clicking! - -# Step 5 - Update the state - -For this, we will use the Tic Tac Toe API. Reading the API, it appears we need -to "Create a new game" to get a "Game ID" so that we can register moves. - -We'll make a small change to our UI to add a button: - -```jsx -

    - Tic Tac Toe - -

    -``` - -And we will define a function `handleNewGame` that uses the API: - -```js -handleNewGame = async () => { - // Make a POST request to ask for a new game - const response = await fetch( - 'https://sdg-tic-tac-toe-api.herokuapp.com/game', - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - } - ) - - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() - - // Make that the new state! - this.setState(game) + function handleButtonClick() { + setCounter(counter + 1) } -} -``` - -This function uses the API to send a request to make a new game. Because we -designed our state to **exactly** match what the API returns, all we need to do -is take the `JSON` object that it returns and place it in the state. - -```js -// Get the response as JSON -const game = await response.json() - -// Make that the new state! -this.setState(game) -``` -Try this a few times in the UI. Use the `React Developer Tools` to look at the -`state` of our component after clicking the new game button. You should see an -empty board but with a new `id` value each time! - -You could also try making the initial state something other than a blank board. -You would see that making a new game will _reset_ it. **Don't forget to put the -default state back to an array of empty strings!** - -The `id` within the `state` will help us when we need to record the game actions -on a click. - -## Update handleClickCell - -When we click a cell, we need to build an API request to send to the server. The -response we get back, as we did with handleNewGame, will be in exactly the form -to use with `this.setState`. - -That code looks like: - -```js -handleClickCell = async (row, column) => { - // Generate the URL we need - const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${this.state.id}` - - // Make an object to send as JSON - const body = { row: row, column: column } - - // Make a POST request to make a move - const response = await fetch(url, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(body), - }) - - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() - - // Make that the new state! - this.setState(game) + function handleChangeInput(event) { + setName(event.target.name) } -} -``` - -Other than sending the url, sending a `body` containing the `row` and `column` -information, the structure of this code is very similar to `handleNewGame`. This -includes the processing of the response: - -```js -// Get the response as JSON -const game = await response.json() - -// Make that the new state! -this.setState(game) -``` - -So as we make a move, we should see the API send us back a game state. - -This game state will have our recorded move, but it will also have **the -computer's move as well** - -Make a new game and try a few moves! - -## Handle the winner - -The API will also tell us the winner of the game. We can make the header display -the winner information. - -To do this, we'll first extract the static data to a variable. - -```jsx - const header = 'Tic Tac Toe' return (
    -

    - {header} - -

    -``` - -Now we can make this string dynamic by using a -[ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) -operator. - -```js -const header = this.state.winner - ? `${this.state.winner} is the winner` - : 'Tic Tac Toe' -``` - -And with this, we have a playable Tic Tac Toe game that demonstrates how to use -an API and React State to make an interactive app! - -# Improve the code - -We can improve the code to remove some duplication in the drawing of the game -board. - -We can use `map` to generate the elements of the board. In this case, since we -have an _array of arrays_ we'll have to use **two** `map` calls. The outer one -will loop through the `rows` and the inner one will loop through the `columns` - -```jsx -
      - {this.state.board.map((boardRow, rowIndex) => { - return boardRow.map((cell, columnIndex) => { - return ( -
    • this.handleClickCell(rowIndex, columnIndex)} - > - {cell} -
    • - ) - }) - })} -
    -``` - -Two dimensional arrays can be tricky at first so study this code. Maybe some -`console.log` will help make the code more clear: - -```jsx -
      - {this.state.board.map((boardRow, rowIndex) => { - console.log(`The rowIndex is ${rowIndex} and the boardRow is ${boardRow}`) - return boardRow.map((cell, columnIndex) => { - console.log( - `-- With the inside loop the columnIndex is ${columnIndex} and the cell is ${cell}` - ) - return ( -
    • this.handleClickCell(rowIndex, columnIndex)} - > - {cell} -
    • - ) - }) - })} -
    -``` - -> **IMPORTANT** -- Any time we generate JSX dynamically such as with a `map` we -> need to include a `key` value for the outer-most element. In this case we need -> a unique value for the `
  • `. The value only needs to be unique to it's -> siblings. So in this case the `columnIndex` is enough to tell React "this is -> the 0th element ... this is the 1st element ... this is the 2nd element" and -> React will be satisfied. - -You may have noticed that if you try to click on a game square before there is a -game created, or after a winner exists, we'll get back some error information -from the API. - -Let's block clicks in these cases: - -- There is no game created - -- The user clicks on an occupied cell - -- Someone has won - -We can do this by introducing the concept of a -[guard clause](). A -`guard clause` is a boolean conditional (typically an `if`) statement that -checks for conditions under which we don't want the rest of the function/method -to execute. Typically inside a `guard clause if statement`, we would see a -`return` statement, which would end the function's execution. - -In our case we want to add this code to the top of our `handleClickCell` -function: - -```js -if ( - // No game id - this.state.id === undefined || - // A winner exists - this.state.winner || - // The space isn't blank - this.state.board[row][column] !== ' ' -) { - return +

    + Hi there {name} The counter is {counter} +

    + +

    + +

    +
  • + ) } ``` -This allows us to block the click for each of the conditions we want to prevent. - -If you look in the `CSS` file, you'll see that we have some styling for cells -that show any cell with a class of `taken` to have a cursor that indicates we -cannot click. This adds a nice visual effect to align with the -`guard clause`protection we just added. - -We can dynamically set the class name of an `li` again using a `ternary` -expression: - -```jsx -
  • this.handleClickCell(rowIndex, columnIndex)} -> - {cell} -
  • -``` - -This code will set the `className` to a blank string if the cell is still open -(equal to space) and to `taken` if there is any other value (say an `X` or an -`O`) - -# Reviewing the Steps for an API based component - -- Step 1 - Static implementation - -- Step 2 - Make a state object containing data - -- Step 3 - Try manually changing the value in the state. - -- Step 4 - Connect actions - -- Step 5 - Update state - -- Step 5a - Use fetch to send required data to the API -- Step 5b - Use the response from fetch to get the new state -- Step 5c - Update the state - -- Step 6 - Refine dynamic nature of UI based on state data +Ah, how nice. We have independent **variables** to track our state, instead of +chained object access (e.g. `name` vs `this.state.name`) and very simple methods +to update the state `setName(event.target.value)` diff --git a/lessons/react-state/lecture.md b/lessons/react-state/lecture.md index 90065756..bf25e076 100644 --- a/lessons/react-state/lecture.md +++ b/lessons/react-state/lecture.md @@ -30,651 +30,344 @@ We have already seen properties. # STATE -- Is a specifically named variable as part of the class - Can be _modified_ - React knows to re-render the UI when the state is changed --- -# A step-by-step approach to building a dynamic UI - ---- +# Hooks -# Step 1 - Static implementation +In a functional component we use a system called `hooks` to implement features such as tracking `state`-ful information. -- Render a static (hardcoded) version of what you want - -```js -export class Counter extends React.Component { - render() { - return ( -
    -

    The count is 0

    - -
    - ) - } -} -``` +The name `hook` comes from the idea that we are `hooking` into React's processing. --- -# Step 2 - Make a state object containing data +# We will start with the simplest `hook` in React, `useState`. -- Put a good initial/default value in the state +`useState` is a React function that allows us to create a variable in our component that can change over time. -```js -export class Counter extends React.Component { - state = { - count: 0, - } - - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` +It comes from the standard React library. --- -# Step 3 - Try manually changing the value in the state. +# Rules of hooks -- See that the UI changes when the state is modified +1. Hooks should all begin with the word `use` and follow `camelCase` names. -```js -export class Counter extends React.Component { - state = { - count: 42, - } - - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` +1. Hooks must be called in the same order each time a compontent renders. The easiest way to guarantee this is to not place a `useXXXX` hook inside of a conditional, or have any "guard clauses" **before** the use of a hook method. --- -# Step 4 - Connect actions +# State changes lead to re-rendering -- Use the `onXXXX` methods to handle events. +This is a key aspect of `state` in React. -```jsx -export class Counter extends React.Component { - state = { - count: 42, - } - - handleClickButton = event => { - // This isn't necessary, but it can be a good habit. - // There is no default behavior for this button, but this would inhibit that behavior if there were. - event.preventDefault() - - console.log('Clicked!') - } - - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` +Each time we change the `state` (using the method we are about to introduce) the `React` system detects this change and then **re-renders** our component with the new information. --- -# Now we know we can connect a method to an event. - ---- +# Demo time! -# Step 5 - Update state +# [fit] Click Counter -- For our button, we want to: - - Get the current count from the state - - Increment it - - Update the state +# [fit] The "Hello World" of interactive web applications! --- -[.column] - -```jsx -export class Counter extends React.Component { - state = { - count: 42, - } - - handleClickButton = (event) => { - event.preventDefault() - - // Get the current count - const currentCount = this.state.count +# A step-by-step approach to building a dynamic UI - // Increment - const newCount = currentCount + 1 +1. Static Implementation +2. Make a state object containing data +3. Try manually changing the value in the state +4. Connect actions (later on, we'll add API interaction here) +5. Update state - // Make a new state - const newState = { - count: newCount, - } +--- - // Tell React about the new state - this.setState(newState) - } -``` +# Step 1 - Static implementation -[.column] +- Render a static (hardcoded) version of what you want -```jsx - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } +```js +export function Counter() { + return ( +
    +

    The count is 0

    + +
    + ) } ``` --- -# Simplify the code +# Step 2 - Introduce State ---- +Add our first hook, known as `useState`. -```jsx -export class Counter extends React.Component { - state = { - count: 42, - } +Here is the code to create the state variables and display their value. We'll then break down this code line-by-line - handleClickButton = event => { - event.preventDefault() +--- - const newState = { - count: this.state.count + 1, - } +```jsx +export function Counter() { + // prettier-ignore + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) - this.setState(newState) - } + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- -# Even more simple - ---- +[.code-highlight: 2-2] + ```jsx -export class Counter extends React.Component { - state = { - count: 42, - } - - handleClickButton = event => { - event.preventDefault() +export function Counter() { + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) - this.setState({ count: this.state.count + 1 }) - } + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` + ---- +- Declares we are going to use some state (e.g. `useState`) +- Sets initial value (e.g. `0`) +- `useState` always returns an array with two entries -# Steps: +--- -- Step 1 - Static implementation +[.autoscale: true] -- Step 2 - Make a state object containing data +# `useState` rules -- Step 3 - Try manually changing the value in the state. +`useState` has a few particular _rules_ that we need to remember: -- Step 4 - Connect actions +1. Value given to `useState` in parenthesis is used as the initial value only the first time the component's instance is rendered. Even if the component is rendered again due to a state change, the state's value isn't reset to the initial value. -- Step 5 - Update state +2. `useState` always returns an _array_ with exactly _two_ elements. The **first** element is the _current value of the state_ and the **second** element is _a function that can change the value of this state_ --- -# A more complex example: Tic Tac Toe With an API - ---- +# Using the `useState` return value -# Step 1 - Static Implementation +[.code-highlight: 4-6] + ```jsx -export class App extends Component { - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    -
    - ) - } +export function Counter() { + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) + + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] + + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` + ---- - -# Step 2: Make a state using data +Creates two local variables +First is the value of the counter +Second is a function that allows us to change the counter -[.column] +--- -- When using an API, taking the data example from the API is a great way to start. +# Simplify (using Destructuring Assignment) -[.column] +```jsx +export function Counter() { + const [counter, setCounter] = useState(0) -```js -{ - "board": [ - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ] - ], - "winner": null, + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` ---- +# [fit] Ah, so much more room for activities... -# Step 2 Continued: +![right fit](https://media.giphy.com/media/yrFrXTTTcHIY0/giphy.gif) -[.column] +--- -```jsx -export class App extends Component { - state = { - board: [ - [' ', ' ', ' '], - [' ', ' ', ' '], - [' ', ' ', ' '], - ], - id: null, - winner: null, - } -``` +# Step 3 - Try manually changing the value in the state. -[.column] +- See that the UI changes when the state is modified ```jsx - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • {this.state.board[0][0]}
    • -
    • {this.state.board[0][1]}
    • -
    • {this.state.board[0][2]}
    • -
    • {this.state.board[1][0]}
    • -
    • {this.state.board[1][1]}
    • -
    • {this.state.board[1][2]}
    • -
    • {this.state.board[2][0]}
    • -
    • {this.state.board[2][1]}
    • -
    • {this.state.board[2][2]}
    • -
    -
    - ) - } +export function Counter() { + const [counter, setCounter] = useState(42) + + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- -# Step 3: Try manually changing the state +# Step 4 - Connect actions [.column] -```jsx -export class App extends Component { - state = { - board: [ - ['X', 'O', 'X'], - [' ', ' ', ' '], - [' ', 'O', ' '], - ], - winner: null, - } -``` +- Create a `handleXXXX` function to handle events. +- We will define this _INSIDE_ our function. Whoa! Nested functions! [.column] ```jsx - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • {this.state.board[0][0]}
    • -
    • {this.state.board[0][1]}
    • -
    • {this.state.board[0][2]}
    • -
    • {this.state.board[1][0]}
    • -
    • {this.state.board[1][1]}
    • -
    • {this.state.board[1][2]}
    • -
    • {this.state.board[2][0]}
    • -
    • {this.state.board[2][1]}
    • -
    • {this.state.board[2][2]}
    • -
    -
    - ) - } -} -``` +export function Counter() { + const [counter, setCounter] = useState(42) ---- - -# See that this affects the user interface - ---- - -# Step 4: Connect the actions + function handleClickButton(event) { + event.preventDefault() -- Define a method that will handle clicking on the cell -- We will need to know the row and column + console.log('Clicked!') + } -```js -handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- - -```jsx -export class App extends Component { - state = { - board: [ - ['X', 'O', 'X'], - [' ', ' ', ' '], - [' ', 'O', ' '], - ], - winner: null, - } +# [fit] Event handlers still receive **event** object - handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) - } +```jsx +function handleClickButton(event) { + event.preventDefault() - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • this.handleClickCell(0, 0)}>{this.state.board[0][0]}
    • -
    • this.handleClickCell(0, 1)}>{this.state.board[0][1]}
    • -
    • this.handleClickCell(0, 2)}>{this.state.board[0][2]}
    • -
    • this.handleClickCell(1, 0)}>{this.state.board[1][0]}
    • -
    • this.handleClickCell(1, 1)}>{this.state.board[1][1]}
    • -
    • this.handleClickCell(1, 2)}>{this.state.board[1][2]}
    • -
    • this.handleClickCell(2, 0)}>{this.state.board[2][0]}
    • -
    • this.handleClickCell(2, 1)}>{this.state.board[2][1]}
    • -
    • this.handleClickCell(2, 2)}>{this.state.board[2][2]}
    • -
    -
    - ) - } + console.log('Clicked!') } ``` ---- - -# Step 5: Update the state - -- For this, we will use the Tic Tac Toe API -- The first step is to make a new game by clicking on `new` +Showing how to _prevent the default behavior_, not typically needed outside of links and form submit. --- -```js -handleNewGame = async () => { - // Make a POST request to ask for a new game - const response = await fetch( - 'https://sdg-tic-tac-toe-api.herokuapp.com/game', - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - } - ) - - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() +# Connect the event - // Make that the new state! - this.setState(game) - } -} -``` +# [fit] Goodbye `addEventListener` ```html -

    Tic Tac Toe -

    + ``` +- We are associating the event, `onClick` with the function `handleClickButton`. +- The `onClick` is actually a property of the DOM element. +- We assign that property to the function itself. + --- -# See that this updates the user interface +# Naming conventions -- Test this by making the elements in the initial `board` state contain something other than spaces! -- Creating a new game should visually "reset" the board +- `onXXXXX` or `handleXXXXX` named methods (e.g. `onClick`, `onChange`, `handleClick`, etc.) +- `_buttonClick` -- because the `_` looks like a _"handle"_ attached to the word `buttonClick` --- -[.column] - -# Notice the state has some extra information in it now +# [fit] Can connect a method to an event! -- We know the _id_ of the game. Useful for future API requests. - -[.column] - -```js -{ - "id": 5, - "board": [ - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ] - ], - "winner": null, - "created_at": "2021-02-19T00:52:49.678Z", - "updated_at": "2021-02-19T00:52:49.678Z" -} -``` +# [fit] Time to update state (finally...) --- -# Now, let us update handleClickCell +# Step 5 - Update state -```jsx -handleClickCell = async (row, column) => { - // Generate the URL we need - const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${this.state.id}` - - // Make an object to send as JSON - const body = { row: row, column: column } - - // Make a POST request to make a move - const response = await fetch(url, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(body), - }) - - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() - - // Make that the new state! - this.setState(game) - } -} -``` +[.column] ---- +- For our button, we want to: + - Get the current count + - Increment it + - Update the state -# Handle the winner +[.column] -- The API gives us information about the winner. -- Let us make the header display the winner +```jsx +export function Counter() { + const [counter, setCounter] = useState(42) ---- + function handleClickButton(event) { + event.preventDefault() -# Make the header a string we generate in the code + // Increment + const newCount = count + 1 -```jsx -render() { - const header = 'Tic Tac Toe' + // Tell React there is a new value for the count + setCount(newCount) + } return (
    -

    - {header} - -

    +

    The counter is {counter}

    + +
    + ) +} ``` --- -# Now make it dynamic +# Warning! Warning! -```js -const header = this.state.winner - ? `${this.state.winner} is the winner` - : 'Tic Tac Toe' -``` +> NOTE: After `setCount` does not change `count` right away. The value isn't changed until React gets a chance to update state **AFTER** our function is done. ---- - -# Remove duplication in the creation of the game board +> This often confuses new React developers. We'll see this again when we use more complex state -- Let us use `map` instead of repeating all the `li` - -```jsx -
      - {this.state.board.map((boardRow, rowIndex) => { - return boardRow.map((cell, columnIndex) => { - return ( -
    • this.handleClickCell(rowIndex, columnIndex)} - > - {cell} -
    • - ) - }) - })} -
    -``` +![fit right](https://media1.tenor.com/images/fb0638add0828e1975144f3dd93e6e62/tenor.gif?itemid=11779622) --- -# Block clicks - -- When there is no game -- Or when the user clicks on an occupied cell -- Or when someone has won - -```js -if ( - // No game id - this.state.id === undefined || - // A winner exists - this.state.winner || - // The space isn't blank - this.state.board[row][column] !== ' ' -) { - return -} -``` - ---- +# Simplify the code -# Dynamically set the class name +```jsx +function CounterWithName() { + const [counter, setCounter] = useState(0) -- If the cell is not empty, set the class to `taken` + function handleButtonClick() { + setCounter(counter + 1) + } -```jsx -
  • this.handleClickCell(rowIndex, columnIndex)} -> - {cell} -
  • + return ( +
    + +
    + ) +} ``` --- @@ -691,8 +384,4 @@ if ( - Step 5 - Update state -- Step 5a - Use fetch to send required data to the API -- Step 5b - Use the response from fetch to get the new state -- Step 5c - Update the state - -- Step 6 - Refine dynamic nature of UI based on state data +--- diff --git a/programs/web-development.yaml b/programs/web-development.yaml index 9baf6b91..3e4ff605 100644 --- a/programs/web-development.yaml +++ b/programs/web-development.yaml @@ -78,8 +78,8 @@ modules: # react-events -- needs work - js-fetch - react-state + - react-state-with-fetch - react-state-flow - - react-hooks - react-hooks-use-effect - react-hooks-react-router - full-stack-development: diff --git a/web/static/lectures/react-intro-lecture.pdf b/web/static/lectures/react-intro-lecture.pdf index 9f60651775426558ff9a911444d50cc561450236..99feb7a8d572b00bd0dcdd6ea16b9e4c1db03eba 100644 GIT binary patch delta 84 zcmV~$u@QhE3e{l?KjBAkB>o)@41 delta 84 zcmWN_u@S%^2mrvdb&8CDAPPfd2m!rIoi42lWaQ+_-)X0}onv$&;Aj?O7~3KT1YLi* fPSGyR3g`ihR!M@Kn-=O)4H-(luV)^D)H>u3<9-${ diff --git a/web/static/lectures/react-state-lecture.pdf b/web/static/lectures/react-state-lecture.pdf index cef73a6b2612caf3918337509c949a20527a7dca..fa33a2a7c4ee0fb8e36454f0f2b99863228809cb 100644 GIT binary patch delta 85 zcmV~$xeb6Y3;;l-%@mB__=~&*I~d!MsOe~707mBSX>a>q_ delta 84 zcmWN@u@QhU2nEoy%@mFR;gd^-a0m==XKiPz130q2>OIT7ylopz5|WizYbtg!C2t@z fbTU_Sx2P`4-GCVxIitIA#6OmD9#^bT@Id(h@x>SY diff --git a/web/static/lectures/react-state-with-fetch-lecture.pdf b/web/static/lectures/react-state-with-fetch-lecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2c2b9e49de68123768ace8bb5b31fccccaf00eab GIT binary patch literal 131 zcmWN?%MrpL6ac_GRips}B;f(+hW8U@R5B8CuzKCgUh*ETzovSg$JnjCxAXSUG5@b0 zoT)$UIE3uwO>aDnT9yF2&noOWBFc(uh^ks(@fz4E5(;Dpi84AWX>|c}CfSH1Xz~n& N&1hel7I1NM<_BBdDJB2_ literal 0 HcmV?d00001 From 8e3124a66c7f8890439183c0a657cac1d14f2752 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Wed, 28 Jul 2021 14:52:52 -0400 Subject: [PATCH 11/13] WIP --- lessons/react-intro/index.md | 41 ++++++++++++++++++++++++---------- lessons/react-intro/lecture.md | 2 +- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lessons/react-intro/index.md b/lessons/react-intro/index.md index 1ec89f6c..eef4c113 100644 --- a/lessons/react-intro/index.md +++ b/lessons/react-intro/index.md @@ -125,7 +125,7 @@ If we rendered this without JavaScript it would be an empty page. It is thus up to JavaScript to connect our React code to our HTML. In our template we include an `main.tsx` -- this script loads React and a -component we provide named `App` +component named `App` ```js import React from 'react' @@ -312,7 +312,13 @@ Let's start that process. ## Creating a `NewsArticle` component. -Let's create a new file in the `components` directory and name it +As we work on creating components it is a good practice to place the files in a +`components` directory within the `src` directory. While this isn't required it +is often considered best practices. + +If your project doesn't already have a `components` directory, make one now. + +Then create a new file in the `components` directory and name it `NewsArticle.tsx`. If you do not already have a `components` directory in your project, you should make one and then create `NewsArticle.tsx` inside. @@ -485,14 +491,25 @@ function. The `props` argument is an object whose keys are the names of the properties. In our case this is `props.title` and `props.body` -- the corresponding values are supplied as well. +When defining the `props` argument we need to define the types for this object. +We will create a `type` named `NewsArticleProps` and declare that the `title` +property shall be a `string` and the `body` property shall be a `string` as +well. When defining the component function we'll declare that `props` has a type +of `NewsArticleProps` + We can use these in our component by using an _interpolation_ method within JSX. This is much like string interpolation in plain JavaScript but the syntax is slightly different: -```javascript +```typescript import React from 'react' -export function NewsArticle(props) { +type NewsArticleProps = { + title: string + body: string +} + +export function NewsArticle(props: NewsArticleProps) { return (

    {props.title}

    @@ -584,7 +601,7 @@ We will be using this in our `App.tsx` so let's import it! ```javascript import React from 'react' import { NewsArticle } from './components/NewsArticle' -import articles from './articles' +import articles from './articles.json' function App() { return ( @@ -610,10 +627,10 @@ function App() { export default App ``` -The line `import articles from './articles'` will read the JSON file and make -its contents available as the variable `articles`! No parsing required! This is -because the environment comes with a **`loader`** for JSON files and it knows -how to read and parse them for us! +The line `import articles from './articles.json'` will read the JSON file and +make its contents available as the variable `articles`! No parsing required! +This is because the environment comes with a **`loader`** for JSON files and it +knows how to read and parse them for us! Let's use that data to build up an array of `` components. @@ -625,7 +642,7 @@ Since we want one `` that is related to each element of the ```javascript import React from 'react' import { NewsArticle } from './components/NewsArticle' -import articles from './articles' +import articles from './articles.json' function App() { const newsArticlesFromData = articles.map(article => ( @@ -720,7 +737,7 @@ where we want them in place of the hardcoded data. ```javascript import React from 'react' import { NewsArticle } from './components/NewsArticle' -import articles from './articles' +import articles from './articles.json' function App() { const newsArticlesFromData = articles.map(article => ( @@ -756,7 +773,7 @@ will us that `id` as our key! ```javascript import React from 'react' import { NewsArticle } from './components/NewsArticle' -import articles from './articles' +import articles from './articles.json' function App() { const newsArticlesFromData = articles.map(article => ( diff --git a/lessons/react-intro/lecture.md b/lessons/react-intro/lecture.md index db4acf4e..00393171 100644 --- a/lessons/react-intro/lecture.md +++ b/lessons/react-intro/lecture.md @@ -135,7 +135,7 @@ If we rendered this without JavaScript it would be an empty page. It is thus up # User Interface all in JavaScript -In our SDG template we include an `main.tsx` -- this script loads React and a component we provide named `App` +In our SDG template we include an `main.tsx` -- this script loads React and a component named `App` ```js import React from 'react' From c8dcb1084ed250f5ac1462eef66c9b2a141b9c17 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Wed, 28 Jul 2021 20:34:00 -0400 Subject: [PATCH 12/13] WIP --- lessons/js-fetch/index.md | 5 +- lessons/react-assets/index.md | 99 +++++++-------- lessons/react-state-with-fetch/index.md | 117 +++++++++++++++++- lessons/react-state-with-fetch/lecture.md | 115 ++++++++++++++++- lessons/react-state/index.md | 83 ++++++++++--- lessons/react-state/lecture.md | 109 ++++++++++++++-- web/static/lectures/react-state-lecture.pdf | Bin 132 -> 132 bytes .../react-state-with-fetch-lecture.pdf | Bin 131 -> 131 bytes 8 files changed, 437 insertions(+), 91 deletions(-) diff --git a/lessons/js-fetch/index.md b/lessons/js-fetch/index.md index d8c2901d..6d933cb8 100644 --- a/lessons/js-fetch/index.md +++ b/lessons/js-fetch/index.md @@ -77,8 +77,9 @@ fetch('https://restcountries.eu/rest/v2/all') - This returns usable information! -```javascript -;[ + +```typescript +[ { name: 'Afghanistan', alpha2Code: 'AF', alpha3Code: 'AFG' }, { name: 'Åland Islands', alpha2Code: 'AX', alpha3Code: 'ALA' }, { name: 'Albania', alpha2Code: 'AL', alpha3Code: 'ALB' }, diff --git a/lessons/react-assets/index.md b/lessons/react-assets/index.md index 5c5c3e68..c1a5cf1c 100644 --- a/lessons/react-assets/index.md +++ b/lessons/react-assets/index.md @@ -2,80 +2,71 @@ title: React Assets --- ---- - ## Using `import` to load assets ---- +We've seen that we can `import` TypeScript code into the current file/module. +We've also seen that we can `import` JSON data from a file into a variable. -## React apps use Webpack +The fact that our development tools give us various `loaders` to deal with +importing various data is very helpful. ---- +## What can we import? -## Webpack is a tool for packaging assets +Our development and build tools come with various `loaders` to able to import +various data types. If there is a type of data we need to support we can either +find an existing loader and add it to our project **or** we can write our own +since a loader is nothing more than some TypeScript (or JavaScript) code that +describes how to read the file and provide a JavaScript object in response. -- JavaScript -- JSON (JavaScript Object Notation) - - a lightweight data-interchange format, representing javascript object in - text - - It is easy for humans to read and write. It is easy for machines to parse - and generate +- TypeScript code +- JSON - Images - Fonts - CSS ---- - -## Webpack comes with various `loaders` to able to import various data types - ---- - -## Webpack pulls from our source and helps generate JavaScript for the browser - ---- - -## When we import an image - -```javascript -import photo from './skywalker.png' -``` +## Importing images in a React application -##### We get a string representing the path to the image to use in code +Your first inclination to deal with images in JSX will be to write code like the +following: -```javascript -render() { - return ( -
      -
    • - -
    • -
    - ) -} +```jsx +Awesomeness Defined ``` ---- +However, you will find that this doesn't work in development or in production. -## When we import a JSON file +The correct method is to `import` the image first: ```javascript -import octocats from `./cats.json` +import image from '../my-awesome-image.png' ``` -##### We get a JSON object we can access and use +This provides you `string` containing the **path** to the image to use in code. -```javascript -render() { - const cats = octocats.map(cat => { - return - }) - - return ( -
    - {cats} -
    - ) +```jsx + return } ``` ---- +## Why do we import images? + +This seems like a big difference to how we dealt with images in plain HTML and +CSS projects. We use this method in our React (and other TypeScript/JavaScript +projects) so that our build tool can add these features: + +1. It ensures that the file name for the image is "slugged". Slugging means that + it takes some unique value and makes that part of the image path. You'll + notice that the string in `image` isn't just `my-awesome-image.png` but + something like `my-awesome-image-dea415f.png` (and maybe even a longer + string). That bit of text after the file name is the "checksum" of the file. + The checksum is a value that changes _any_ time the contents of the file + change. We do this because we'd like to **cache** the images for a long time + on our client's browsers. By having a checksum we can set a _long_ caching + time but ensure that the cient fetches a fresh image when we change the + contents. Since the image will have a new file name when the contents change, + we achieve both caching and freshness. +2. It also allows the **deploy** process to only upload the images that are + _used_ in the code. Unused images won't have an `import` and thus won't be + included in the deployment to our hosting system. +3. Some loaders will do image optimizations to ensure the file is as small as + possible. diff --git a/lessons/react-state-with-fetch/index.md b/lessons/react-state-with-fetch/index.md index 259daf50..0154fa90 100644 --- a/lessons/react-state-with-fetch/index.md +++ b/lessons/react-state-with-fetch/index.md @@ -250,7 +250,7 @@ In this case we'll need to know the row and column of the cell so we might write our `handleClickCell` method like this: ```js -function handleClickCell(row, column) { +function handleClickCell(row: number, column: number) { console.log(`You clicked on row ${row} and column ${column}`) } ``` @@ -319,7 +319,7 @@ async function handleNewGame() { } ) - if (response.status === 201) { + if (response.ok) { // Get the response as JSON const newGame = await response.json() @@ -361,7 +361,7 @@ form to use with `setGame`. That code looks like: ```js -async function handleClickCell(row, column) { +async function handleClickCell(row: number, column: number) { // Generate the URL we need const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${game.id}` @@ -375,7 +375,7 @@ async function handleClickCell(row, column) { body: JSON.stringify(body), }) - if (response.status === 201) { + if (response.ok) { // Get the response as JSON const newGame = await response.json() @@ -571,3 +571,112 @@ existing ones based on what we've learned about managing state. - Step 5c - Update the state - Step 6 - Refine dynamic nature of UI based on state data + +## Refining our TypeScript + +Notice that the `json` we are sending to `setGame` has a data type of `any`. +This is because `response.json()` cannot know the data type it is processing. + +We also don't have a very flexible data type for our `game` state. Since we are +providing an object to the initial state, this is the only structure the `game` +will know. + +We need to define some types for the state if we want better type checking in +our code. + +We might start with this: + +```typescript +type Game = { + board: [ + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '], + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '], + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '] + ] + id: null | number + winner: null | string +} +``` + +Here we define `board` as a two dimensional array of three rows and three +columns where each element is either an `'X'`, an `'O'`, or a `' '`. While this +works we can reduce the repetition by defining a `Square` type. + +```typescript +type Square = 'X' | 'O' | ' ' + +type Game = { + board: [ + [Square, Square, Square], + [Square, Square, Square], + [Square, Square, Square] + ] + id: null | number + winner: null | string +} +``` + +This is better. but we **could** go down the path even further: + +```typescript +type Square = 'X' | 'O' | ' ' +type Row = [Square, Square, Square] + +type Game = { + board: [Row, Row, Row] + id: null | number + winner: null | string +} +``` + +This defines the board even more simply as three `Row` types. (and who doesn't +like saying `Row, Row, Row` without thinking +`your boat gently down the stream`). However we can take one more step: + +```typescript +type Square = 'X' | 'O' | ' ' +type Row = [Square, Square, Square] +type Board = [Row, Row, Row] + +type Game = { + board: Board + id: null | number + winner: null | string +} +``` + +Once we have this type we can use it in a few places. First, we will set the +`Game` type on our `useState` + +```typescript +const [game, setGame] = useState({ + board: [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '], + ], + id: null, + winner: null, +}) +``` + +The second place we can use the type is to **force** the `response.json()` to +indicate it shares the shape of the `Game` type. + +```typescript +const newGame = (await response.json()) as Game +``` + +While this did not **remove** any errors from our code it does increaase our +type safety. + +## Warning! + +You might be thinking to yourself: "Oh this is good, if the data from +`response.json()` isn't in the right shape of a `Game` we will find out! + +Unfortunately, `TypeScript` only checks types while in development mode. When we +**RUN** our application, all of the type information is stripped away and +nothing is checked while our code is `executing`. This is a downside to +TypeScript that might be improved in future versions. Future versions may add +what we'd call **run-time type checking** diff --git a/lessons/react-state-with-fetch/lecture.md b/lessons/react-state-with-fetch/lecture.md index c6338f45..d77ae837 100644 --- a/lessons/react-state-with-fetch/lecture.md +++ b/lessons/react-state-with-fetch/lecture.md @@ -135,7 +135,7 @@ const [game, setGame] = useState({ - We will need to know the row and column ```js -function handleClickCell(row, column) { +function handleClickCell(row: number, column: number) { console.log(`You clicked on row ${row} and column ${column}`) } ``` @@ -154,7 +154,7 @@ function App() { winner: null, }) - function handleClickCell(row, column) { + function handleClickCell(row: number, column: number) { console.log(`You clicked on row ${row} and column ${column}`) } @@ -199,7 +199,7 @@ async function handleNewGame() { } ) - if (response.status === 201) { + if (response.ok) { // Get the response as JSON const newGame = await response.json() @@ -263,7 +263,7 @@ async function handleNewGame() { # Update handleClickCell ```jsx -async function handleClickCell(row, column) { +async function handleClickCell(row: number, column: number) { // Generate the URL we need const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${id}` @@ -277,7 +277,7 @@ async function handleClickCell(row, column) { body: JSON.stringify(body), }) - if (response.status === 201) { + if (response.ok) { // Get the response as JSON const newGame = await response.json() @@ -395,3 +395,108 @@ if ( - Step 5c - Update the state - Step 6 - Refine dynamic nature of UI based on state data + +--- + +# Refining our TypeScript + +- `response.json()` returns `any` so data sent to `setGame` has a data type of `any` +- also don't have a very flexible data type for our `game` state + +--- + +# Game state + +```typescript +type Game = { + board: [ + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '], + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '], + ['X' | 'O' | ' ', 'X' | 'O' | ' ', 'X' | 'O' | ' '] + ] + id: null | number + winner: null | string +} +``` + +--- + +# [fit] Game state with type for each square + +```typescript +type Square = 'X' | 'O' | ' ' + +type Game = { + board: [ + [Square, Square, Square], + [Square, Square, Square], + [Square, Square, Square] + ] + id: null | number + winner: null | string +} +``` + +--- + +# Row, Row, Row (your :boat:) + +```typescript +type Square = 'X' | 'O' | ' ' +type Row = [Square, Square, Square] + +type Game = { + board: [Row, Row, Row] + id: null | number + winner: null | string +} +``` + +--- + +# Board type for game state + +```typescript +type Square = 'X' | 'O' | ' ' +type Row = [Square, Square, Square] +type Board = [Row, Row, Row] + +type Game = { + board: Board + id: null | number + winner: null | string +} +``` + +--- + +# Using game state + +```typescript +const [game, setGame] = useState({ + board: [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '], + ], + id: null, + winner: null, +}) +``` + +--- + +# Using game state + +```typescript +const newGame = (await response.json()) as Game +``` + +--- + +# Warning! + +- `TypeScript` only checks types while in development mode! + +- When we **RUN** our application, all of the type information is stripped away and nothing is checked. +- This might be improved in future versions diff --git a/lessons/react-state/index.md b/lessons/react-state/index.md index 4633d29b..82a6c66d 100644 --- a/lessons/react-state/index.md +++ b/lessons/react-state/index.md @@ -254,7 +254,7 @@ Both of these approaches show that if there were **some** way to change the state the UI would automatically update to display the new value of the counter! > NOTE: This is an important step. For this example, it seems simple. Later we -> will be dealing with much more complex state variables and changing the value +> will be dealing with much morxe complex state variables and changing the value > to see how our component "reacts" will be more critical. # Step 4 - Connect actions @@ -268,8 +268,8 @@ interaction. We would pass this function as an event handling function. In React, the event handling function is still proper. However, we will connect it to the event in a different way. -```js -function handleClickButton(event) { +```typescript +function handleClickButton(event: MouseEvent) { event.preventDefault() console.log('Clicked!') @@ -335,12 +335,12 @@ For our button, we want to: That code looks like this: -```js +```typescript // Increment -const newCount = count + 1 +const newCounter = counter + 1 // Tell React there is a new value for the count -setCount(newCount) +setCounter(newCounter) ``` > NOTE: After calling `setCount` you will see that `count` has **NOT** been @@ -354,7 +354,7 @@ We can simplify this code when we place it in our function: function handleClickButton(event) { event.preventDefault() - setCount(count + 1) + setCounter(counter + 1) } ``` @@ -388,12 +388,56 @@ function CounterWithName() { - Step 5 - Update state -### Adding more state +# A note on types + +You may have noticed that when declaring these variables we did **not** have to +specify a type: + +```typescript +const [counter, setCounter] = useState(0) +``` + +However, TypeScript knows that `counter` is an `number` and `setCounter` is a +function that accepts a `number` as an argument. + +This is because the React developers provided type information for all of their +code. They also made their code, such as `useState` able to provide type +inference based on the **initial state** value. + +If we did not provide an initial state, React would **not** be able to infer the +type. Here is an example of that type of `useState` + +```typescript +const [price, setPrice] = useState() +``` + +In this example TypeScript will set a type of `undefined` to `price`. When we +try to `setPrice(42)` (or any other number) we'll receive a TypeScript error +that we cannot assign `number` to `undefined`. + +In the case where we do **not** provide an initial value to `useState` we +_should_ provide a type. + +```typescript +const [price, setPrice] = useState() +``` + +In this case the type of `price` is actually `undefined | number`. That is, +`price` can either have the value of `undefined` **OR** any `number`. This is +very powerful but only if this is the programmers intent. If you never intend +for `price` to `undefined` then we should disallow this by specifying an inital +value. -Let us say we also wanted to keep track of a person's name on the counter. +This is the reason that we **strongly** recommend always using an initial value +for all of your `useState` hooks. If you _cannot_ set an initial value you must +be consider the impact that allowing an `undefined` value in a state variable +will have. -With `hooks`, we will make two **independent** states that each track a single -piece of information. +## Adding more state + +What if we also wanted to keep track of a person's name on the counter? With +`hooks`, we will make two **independent** states that each track a single piece +of information. Separating these pieces of state has a few benefits: @@ -403,7 +447,7 @@ Separating these pieces of state has a few benefits: 2. We can more easily tell where in the code a piece of state or a state changing function is used. -```jsx +```tsx function CounterWithName() { const [counter, setCounter] = useState(0) const [name, setName] = useState('Susan') @@ -412,8 +456,8 @@ function CounterWithName() { setCounter(counter + 1) } - function handleChangeInput(event) { - setName(event.target.name) + function handleChangeInput(event: React.ChangeEvent) { + setName(event.target.value) } return ( @@ -430,6 +474,17 @@ function CounterWithName() { } ``` +# handleChangeInput + +In this function we need to specifically declare the `event` as a data type that +indicates this is a `React.ChangeEvent` on an element that is an +`HTMLInputElement`. This allows `event.target` and `event.target.value` to have +types. Without this specific code for `event`, TypeScript cannot ensure that +`event.target`isn't possibly`null`as well as recognize +that`event.target.value`is a`string` + +# Two independent states + Ah, how nice. We have independent **variables** to track our state, instead of chained object access (e.g. `name` vs `this.state.name`) and very simple methods to update the state `setName(event.target.value)` diff --git a/lessons/react-state/lecture.md b/lessons/react-state/lecture.md index bf25e076..9a412b8b 100644 --- a/lessons/react-state/lecture.md +++ b/lessons/react-state/lecture.md @@ -189,13 +189,13 @@ export function Counter() { ``` -Creates two local variables -First is the value of the counter -Second is a function that allows us to change the counter +^ Creates two local variables +^ First is the value of the counter +^ Second is a function that allows us to change the counter --- -# Simplify (using Destructuring Assignment) +# [fit] Simplify (using Destructuring Assignment) ```jsx export function Counter() { @@ -237,18 +237,16 @@ export function Counter() { # Step 4 - Connect actions -[.column] - - Create a `handleXXXX` function to handle events. - We will define this _INSIDE_ our function. Whoa! Nested functions! -[.column] +--- ```jsx export function Counter() { const [counter, setCounter] = useState(42) - function handleClickButton(event) { + function handleClickButton(event: MouseEvent) { event.preventDefault() console.log('Clicked!') @@ -267,8 +265,11 @@ export function Counter() { # [fit] Event handlers still receive **event** object +- Except now we **must** provide a specific type. +- Type depends on what _kind_ of handler this is. + ```jsx -function handleClickButton(event) { +function handleClickButton(event: MouseEvent) { event.preventDefault() console.log('Clicked!') @@ -321,7 +322,7 @@ Showing how to _prevent the default behavior_, not typically needed outside of l export function Counter() { const [counter, setCounter] = useState(42) - function handleClickButton(event) { + function handleClickButton(event: MouseEvent) { event.preventDefault() // Increment @@ -372,6 +373,92 @@ function CounterWithName() { --- +# Adding more state + +What if we also wanted to keep track of a person's name on the counter? + +With `hooks`, we will make two **independent** states that each track a single piece of information. + +--- + +```jsx +function CounterWithName() { + const [counter, setCounter] = useState(0) + const [name, setName] = useState('Susan') + + function handleButtonClick() { + setCounter(counter + 1) + } + + function handleChangeInput(event: React.ChangeEvent) { + setName(event.target.value) + } + + return ( +
    +

    + Hi there {name} The counter is {counter} +

    + +

    + +

    +
    + ) +} +``` + +--- + +# handleChangeInput + +- Declare the `event` as a data type that indicates this is a `React.ChangeEvent` on an element that is an `HTMLInputElement` +- Allows `event.target` and `event.target.value` to have + types. + +--- + +# A note on types + +--- + +You may have noticed that when declaring these variables we did **not** have to specify a type: + +```typescript +const [counter, setCounter] = useState(0) +``` + +--- + +If we did not provide an initial state, React would **not** be able to infer the type. Here is an example of that type of `useState` + +```typescript +const [price, setPrice] = useState() +``` + +- TypeScript will set a type of `undefined` to `price`. +- When we try to `setPrice(42)` (or any other number) we'll receive a TypeScript error that we cannot assign `number` to `undefined`. + +--- + +In the case where we do **not** provide an initial value to `useState` we _should_ provide a type. + +```typescript +const [price, setPrice] = useState() +``` + +`price` has a type of `undefined | number`. + +--- + +# Always set default state value + +This is the reason that we **strongly** recommend always using an initial value for all of your `useState` hooks. + +If you _cannot_ set an initial value you must be consider the impact that allowing an `undefined` value in a state variable will have. + +--- + # Steps: - Step 1 - Static implementation @@ -383,5 +470,3 @@ function CounterWithName() { - Step 4 - Connect actions - Step 5 - Update state - ---- diff --git a/web/static/lectures/react-state-lecture.pdf b/web/static/lectures/react-state-lecture.pdf index fa33a2a7c4ee0fb8e36454f0f2b99863228809cb..6228da77024e50666e72230c2f072d21cbff62b3 100644 GIT binary patch delta 85 zcmV~$xeb6Y3;;mUW(r0~?AWrGUhMFN{c!+vI@~rmUZ=Fc2Fr@tgk{8VY delta 85 zcmV~$xeb6Y3;;l-%@mB__=~&*I~d!MsOhLP03&m^+istpXo;k#6fuRAabba5P}mZ; elB$)L=tAnHE^uNQ>? diff --git a/web/static/lectures/react-state-with-fetch-lecture.pdf b/web/static/lectures/react-state-with-fetch-lecture.pdf index 2c2b9e49de68123768ace8bb5b31fccccaf00eab..203961534b1366ce833ccfef35bc96faa149e5f1 100644 GIT binary patch delta 84 zcmV~$u@QhE3H_k@D1IiCBnix|6 From 12a0821242051d1f98a44399447c1bc135283f06 Mon Sep 17 00:00:00 2001 From: Gavin Stark Date: Wed, 28 Jul 2021 21:47:08 -0400 Subject: [PATCH 13/13] Update react state flow --- lessons/react-state-flow/lecture.md | 385 +++++++++++------- .../lectures/react-state-flow-lecture.pdf | Bin 131 -> 131 bytes 2 files changed, 231 insertions(+), 154 deletions(-) diff --git a/lessons/react-state-flow/lecture.md b/lessons/react-state-flow/lecture.md index eac8897c..7aa5089f 100644 --- a/lessons/react-state-flow/lecture.md +++ b/lessons/react-state-flow/lecture.md @@ -8,58 +8,86 @@ Theme: Next, 1 --- -[.column] - ```jsx -import React, { Component } from 'react' +import React, { useState } from 'react' +import { NewsArticle } from './components/NewsArticle' +import articles from './articles.json' + +type Square = 'X' | 'O' | ' ' +type Row = [Square, Square, Square] +type Board = [Row, Row, Row] + +type Game = { + board: Board + id: null | number + winner: null | string +} + +``` + +--- -export class App extends Component { - state = { +```typescript +function App() { + const [game, setGame] = useState({ board: [ [' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' '], ], + id: null, winner: null, - } + }) ``` --- -```jsx -handleClickCell = async (row, column) => { +```typescript +async function handleClickCell(row: number, column: number) { if ( - this.state.id === undefined || - this.state.winner || - this.state.board[row][column] !== ' ' + // No game id + game.id === undefined || + // A winner exists + game.winner || + // The space isn't blank + game.board[row][column] !== ' ' ) { return } - console.log(`I clicked on row ${row} and column ${column}`) - - const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${this.state.id}` + // Generate the URL we need + const url = `https://sdg-tic-tac-toe-api.herokuapp.com/game/${game.id}` + // Make an object to send as JSON const body = { row: row, column: column } +``` +--- + +```typescript + // Make a POST request to make a move const response = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }) - if (response.status === 201) { - const game = await response.json() + if (response.ok) { + console.log('x') + // Get the response as JSON + const newGame = (await response.json()) as Game - this.setState(game) + // Make that the new state! + setGame(newGame) } } ``` --- -```jsx -handleNewGame = async () => { +```typescript +async function handleNewGame() { + // Make a POST request to ask for a new game const response = await fetch( 'https://sdg-tic-tac-toe-api.herokuapp.com/game', { @@ -68,51 +96,52 @@ handleNewGame = async () => { } ) - if (response.status === 201) { - const game = await response.json() + if (response.ok) { + // Get the response as JSON + const newGame = (await response.json()) as Game - this.setState(game) + // Make that the new state! + setGame(newGame) } } ``` --- -```jsx -render() { - const header = this.state.winner - ? `Winner is ${this.state.winner}` - : 'Tic Tac Toe' - - return ( -
    -

    - {header} - -

    -
      -
    • this.handleClickCell(0, 0)}> - {this.state.board[0][0]} -
    • -
    • this.handleClickCell(0, 1)}> - {this.state.board[0][1]} -
    • -
    • this.handleClickCell(0, 2)}> - {this.state.board[0][2]} -
    • - {/* Other rows removed for readability */} -
    -
    - ) -} +```typescript +const header = game.winner ? `${game.winner} is the winner` : 'Tic Tac Toe' ``` --- -# Lots of repeated code in the `li` for game cells +```jsx +return ( +
    +

    + {header} - +

    +
      + {game.board.map((boardRow, rowIndex) => { + return boardRow.map((cell, columnIndex) => { + return ( +
    • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
    • + ) + }) + })} +
    +
    +) +``` --- -# Let us extract a component! +# Let us extract a component for each cell! --- @@ -122,17 +151,15 @@ render() { - What else is needed? ```jsx -export class Cell extends Component { - render() { - return ( -
  • this.handleClickCell(0, 0)} - > - {this.state.board[0][0]} -
  • - ) - } +export function Cell() { + return ( +
  • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
  • + ) } ``` @@ -140,9 +167,9 @@ export class Cell extends Component { # Needed -- row -- column -- the board +- rowIndex +- columnIndex +- cell - something to handle clicking the cell --- @@ -164,62 +191,69 @@ export class Cell extends Component { --- ```jsx -export class Cell extends Component { - render() { - return ( -
  • - {this.props.value} -
  • - ) - } +type CellProps = { + rowIndex: number + columnIndex: number + cell: string +} + +export function Cell(props) { + return ( +
  • handleClickCell(props.rowIndex, props.columnIndex)} + > + {props.cell} +
  • + ) } ``` --- -# Define a local click handler +Define a local click handler ```jsx -export class Cell extends Component { - handleClickCell = () => { - console.log(`You clicked on ${this.props.row} and ${this.props.column}`) - } +type CellProps = { + rowIndex: number + columnIndex: number + cell: string +} - render() { - return ( -
  • - {this.props.value} -
  • - ) +export function Cell(props) { + function handleClickCell() { + console.log(`You clicked on ${props.rowIndex} and ${props.columnIndex}`) } + + return ( +
  • handleClickCell(props.rowIndex, props.columnIndex)} + > + {props.cell} +
  • + ) } ``` -- Do not need the inline-arrow-function trick! - --- # This is already better! ```jsx
      - - - - - - - - - - - + {game.board.map((boardRow, rowIndex) => { + return boardRow.map((cell, columnIndex) => { + return ( + + ) + }) + })}
    ``` @@ -235,7 +269,7 @@ export class Cell extends Component { # State down -- We are sending the state DOWN by doing something like `value={this.state.board[0][2]}` +- We are sending the state DOWN by doing something like `cell={cell}` - This _sends_ the **PARENT**'s state to the **CHILD** as `props` --- @@ -250,17 +284,13 @@ export class Cell extends Component { --- ```jsx - - - - - - - - - - - + ``` --- @@ -272,11 +302,16 @@ export class Cell extends Component { --- ```js -handleClickCell = () => { - console.log(`You clicked on ${this.props.row} and ${this.props.column}`) +type CellProps = { + rowIndex: number + columnIndex: number + cell: string + recordMove: (rowIndex: number, columnIndex: number) +} +function handleClickCell() { // Send the event UPwards by calling the `recordMove` function we were given - this.props.recordMove(this.props.row, this.props.column) + props.recordMove(props.rowIndex, props.columnIndex) } ``` @@ -392,18 +427,18 @@ so the UI draws the --- -# Explore some cleanup using JavaScript syntax sugar +# Explore some cleanup using TypeScript syntax sugar - Object shortcut -```js +```typescript const body = { row: row, column: column } ``` - The key name `row` is the same as the name of the variable holding the value `row` - Shortcut (structuring the object): -```js +```typescript const body = { row, column } ``` @@ -415,27 +450,7 @@ const body = { row, column } --- -# `this.props.row`, `this.props.value`, `this.prop.recordMove`, ... - -```jsx -export class Cell extends Component { - handleClickCell = () => { - console.log(`You clicked on ${this.props.row} and ${this.props.column}`) - this.props.recordMove(this.props.row, this.props.column) - } - - render() { - return ( -
  • - {this.props.value} -
  • - ) - } -} -``` +# `props.rowIndex`, `props.columnIndex`, `props.cell`, `prop.recordMove`, ... --- @@ -473,29 +488,57 @@ Notice the `{ }` braces are on the _left_, and the object is on the _right_ # Back to our `Cell` -[.column] - ```jsx -handleClickCell = () => { - const { row, column, recordMove } = this.props +type CellProps = { + rowIndex: number + columnIndex: number + cell: string +} - console.log(`You clicked on ${row} and ${column}`) - recordMove(row, column) +export function Cell(props) { + function handleClickCell() { + // Send the event UPwards by calling the `recordMove` function we were given + props.recordMove(props.rowIndex, props.columnIndex) + } + + return ( +
  • handleClickCell(props.rowIndex, props.columnIndex)} + > + {props.cell} +
  • + ) } ``` -[.column] +--- + +# Destructuring `props` at the top of a function + +--- ```jsx -render() { - const { value } = this.props +type CellProps = { + rowIndex: number + columnIndex: number + cell: string +} + +export function Cell(props) { + const { rowIndex, columnIndex, cell, recordMove} = props + + function handleClickCell() { + // Send the event UPwards by calling the `recordMove` function we were given + recordMove(rowIndex, columnIndex) + } return (
  • handleClickCell(rowIndex, columnIndex)} > - {value} + {cell}
  • ) } @@ -503,7 +546,41 @@ render() { --- -# Destructuring `this.props` at the top of a function +We can destructure the props right in the function declaration. + +```jsx +type CellProps = { + rowIndex: number + columnIndex: number + cell: string +} + +export function Cell({ rowIndex, columnIndex, cell, recordMove}) { + function handleClickCell() { + // Send the event UPwards by calling the `recordMove` function we were given + recordMove(rowIndex, columnIndex) + } + + return ( +
  • handleClickCell(rowIndex, columnIndex)} + > + {cell} +
  • + ) +} +``` + +--- - ... makes it feel like the properties are nice local variables. - ... and that syntax is sometimes more straightforward and tidier. + +--- + +# [fit] State ↓ + +
    + +# [fit] Events ↑ diff --git a/web/static/lectures/react-state-flow-lecture.pdf b/web/static/lectures/react-state-flow-lecture.pdf index 70ea306e5e5df48614298b7df981901a66405290..48ba3e6b69fcce7509c602948e3bc6582eca03d6 100644 GIT binary patch delta 83 zcmWN@yAgmO3;@uxWeP`td?4AuC1ObKtnF-Bz>)P;Z*6OP$C0M$bOMIEQj^HpdLXAx eAs_%xVEoTMn`=}^vq98@Yadqb>xo?mS)hF6E*8}Q delta 83 zcmWN`!3}^g2mrwN?G%mxlv3EiB@k-j+n?Vq;K=rO$<1wUUq9HuYI3uor0hTh;Tgt` f)onECp{`<*2zfH~CBX|J);B88{f=EhkX!iypQsg~