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-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-enumeration/enumeration.md b/lessons/js-enumeration/enumeration.md index 9710144c..e9c3d670 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 its 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 it to use `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}` ) ``` diff --git a/lessons/js-enumeration/lecture.md b/lessons/js-enumeration/lecture.md index b41b5cee..53e36824 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/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/js-intro/arrays.md b/lessons/js-intro/arrays.md index 66923ae6..a1ff4352 100644 --- a/lessons/js-intro/arrays.md +++ b/lessons/js-intro/arrays.md @@ -12,12 +12,18 @@ 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]; +```typescript +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: - -```javascript -let array = new Array(arrayLength) -let array = Array(arrayLength) +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. -// This has exactly the same effect -let array = [] +```typescript +const array: number[] = [] array.length = arrayLength ``` @@ -42,18 +45,26 @@ array.length = arrayLength You can populate an array by assigning values to its elements. For example, -```javascript -let employees = [] +```typescript +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']` + +You **can** have an array variable that you can neither assign a new value, +**or** change the contents of: -```javascript -let myArray = new Array('Hello', myVar, 3.14159) -let myArray = ['Mango', 'Apple', 'Orange'] +```typescript +const cantChangeTheseValues: ReadonlyArray = [42, 100, 52] + +cantChangeTheseValues[0] = 1 ``` ## Referring to array elements @@ -61,8 +72,8 @@ let myArray = ['Mango', 'Apple', 'Orange'] 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'] +```typescript +const myArray = ['Wind', 'Rain', 'Fire'] ``` You then refer to the first element of the array as `myArray[0]` and the second @@ -75,17 +86,20 @@ 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 -let colors = ['red', 'green', 'blue'] +```typescript +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'] +```typescript +const colors = ['red', 'green', 'blue'] colors.forEach(function (color) { console.log(color) }) @@ -94,8 +108,8 @@ colors.forEach(function (color) { Alternatively, You can shorten the code for the forEach parameter with Arrow Functions: -```javascript -let colors = ['red', 'green', 'blue'] +```typescript +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'] +```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/assets/car-type-error.png b/lessons/js-intro/assets/car-type-error.png new file mode 100644 index 00000000..d63ab29e Binary files /dev/null and b/lessons/js-intro/assets/car-type-error.png differ 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 00000000..6b24ebe2 Binary files /dev/null and b/lessons/js-intro/assets/car-type-should-be-error.png differ diff --git a/lessons/js-intro/assets/car-type.png b/lessons/js-intro/assets/car-type.png new file mode 100644 index 00000000..c6bf3bcf Binary files /dev/null and b/lessons/js-intro/assets/car-type.png differ 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 00000000..845315b4 Binary files /dev/null and b/lessons/js-intro/assets/printit-error-2.png differ diff --git a/lessons/js-intro/assets/printit-error.png b/lessons/js-intro/assets/printit-error.png new file mode 100644 index 00000000..675d34ed Binary files /dev/null and b/lessons/js-intro/assets/printit-error.png differ diff --git a/lessons/js-intro/assets/type-inference.png b/lessons/js-intro/assets/type-inference.png new file mode 100644 index 00000000..a8ef6888 Binary files /dev/null and b/lessons/js-intro/assets/type-inference.png differ diff --git a/lessons/js-intro/basics.md b/lessons/js-intro/basics.md index 95377fc7..95b0e897 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..419095f0 100644 --- a/lessons/js-intro/closures.md +++ b/lessons/js-intro/closures.md @@ -5,16 +5,17 @@ order: 6 ## Closures -> NOTE: "What is a closure in JavaScript?" is often an interview question. +> NOTE: "What is a closure in JavaScript/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,12 +49,18 @@ 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 -const people = [ +```typescript +type Person = { + name: string + birthDate: string + delayMilliseconds: number +} + +const people: Person[] = [ { name: 'Alan Turing', birthDate: 'June 23, 1912', @@ -80,8 +87,8 @@ const people = [ Then we will create a method that accepts a person variable and prints out details about them. -```javascript -function printPersonInfo(person) { +```typescript +function printPersonInfo(person: Person) { console.log(`${person.name} was born on ${person.birthDate}`) } ``` @@ -91,7 +98,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 diff --git a/lessons/js-intro/control-flow.md b/lessons/js-intro/control-flow.md index 8ba5be3a..caef6179 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. @@ -11,10 +11,10 @@ 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: -```javascript +```typescript { statement_1 statement_2 @@ -25,10 +25,26 @@ The block is delimited by a pair of curly brackets: } ``` +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 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 +53,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 +63,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 +73,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 +90,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 +106,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 +140,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/index.md b/lessons/js-intro/index.md index 336ff390..47f4c8f3 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,49 +7,31 @@ 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 +representing a web page—in memory. Usually that means TypeScript, although modeling HTML, SVG, or XML documents as objects is not part of the JavaScript language, as such. diff --git a/lessons/js-intro/lecture.md b/lessons/js-intro/lecture.md index 1524f3c4..c3e67e1b 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. The `person` variable is // recreated each time through the forEach loop. Since it is an argument to the 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 02647df4..0f276610 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** + +```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, +} -```javascript -myCar.color // undefined +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,43 +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 TypeScript string, or anything that can +be converted to a string, including the empty string. 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. This notation is also very useful when property +names are to be dynamically determined (when the property name is not determined +until runtime). -```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' -// } -``` - -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 @@ -126,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' } ``` @@ -143,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, @@ -154,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, @@ -164,32 +220,32 @@ 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 -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: -```javascript -let myOtherCar = { +```typescript +const myOtherCar = { ...myCar, year: 1971, } ``` Much better, we've saved one line of code, however, it now doesn't matter how -many property/value pairs `myCar` has since they are all now part of -`myOtherCar` since this is effectively what JavaScript is doing for us: +many property/value paris `myCar` has since they are all now part of +`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, @@ -207,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, @@ -216,7 +272,7 @@ let myOtherCar = { // is the same as: -let myOtherCar = { +const myOtherCar = { year: 1971, make: myCar.make, @@ -228,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 1a118073..fc1ba7c2 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 931ef32c..3754f946 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,159 @@ 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. +``` -```javascript -let name; -// name contains 'undefined' +## Better form -name = "Jane"; +```typescript +let name: string +// name contains 'undefined' and should be of type `string` + +name = 'Jane' // name now contains the value 'Jane' ``` +## Best form + +```typescript +const name = 'Jane' // name contains the value 'Jane' and we avoid any issue with `undefined` +``` + 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 -its value later. Once we introduce conditions and functions we will see cases -of this. +its value later. Once we introduce conditions and functions we will see cases of +this. -# 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 +216,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 +331,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 +355,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 that we have data, we need ways to manipulate the variables 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 8cfbc839..984150a2 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/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-intro/index.md b/lessons/react-intro/index.md index 8a02cd94..2055b08f 100644 --- a/lessons/react-intro/index.md +++ b/lessons/react-intro/index.md @@ -42,52 +42,75 @@ 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? -JSX is an extension of JavaScript that allows us to use an HTML-like syntax in our -JavaScript code that will be turned into DOM at run-time. By using JSX we can -dynamically generate content and use a [UI as state](lessons/js-ui-as-state) +JSX is an extension of JavaScript that allows us to use an HTML-like syntax in +our JavaScript code that will be turned into DOM at run-time. By using JSX we +can dynamically generate content and use a [UI as state](lessons/js-ui-as-state) 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 do we use React Components? ## How does our template help us 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 our app template 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 +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. The template SDG uses will generate an `index.html` file that looks like the @@ -102,7 +125,7 @@ following (only the `` is shown and only the relevant 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 we include a `main.js` -- this script loads React and a +In our SDG template we include a `main.tsx` -- this script loads React and a component we provide named `App` ```js @@ -111,20 +134,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 @@ -139,6 +167,8 @@ Let's generate a new project that will set up a new React project. degit $GITHUB_USER/react-project-template ReactArticles ``` +> > > > > > > master + This is some sample HTML we will work to create and learn how React Components can help simplify our code. @@ -175,40 +205,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 @@ -292,8 +319,15 @@ Let's start that process. ## Creating a `NewsArticle` component. -Let's create a new file in the `components` directory and name it -`NewsArticle.jsx` +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. We'll add this as the first line: @@ -307,21 +341,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
} ``` @@ -329,21 +363,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 @@ -357,33 +391,27 @@ This means our `NewsArticle` component is rendering itself. ## Composition This is the idea of `composition` -- we are now defining a component, that has -its 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 ( + + ) } ``` @@ -394,19 +422,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 @@ -416,22 +442,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 @@ -468,36 +492,45 @@ 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. + +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 class NewsArticle extends React.Component { - render() { - return ( - - ) - } +type NewsArticleProps = { + title: string + body: string +} + +export function NewsArticle(props: NewsArticleProps) { + 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** @@ -508,30 +541,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 @@ -542,7 +573,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. @@ -572,45 +603,43 @@ 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 ( -
-
- - - -
-
- ) - } +import articles from './articles.json' + +function App() { + return ( +
+
+ + + +
+
+ ) } 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. +Let's use that data to build up an array of `` components. ## Transforming data into components @@ -618,35 +647,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 ( -
-
- - - -
-
- ) - } +import articles from './articles.json' + +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
+ + + +
+
+ ) } export default App @@ -662,10 +689,10 @@ const newsArticlesFromData = articles.map(article => ( )) ``` -In our [lesson on JavaScript iteration](/lessons/js-iteration] we -demonstrated that `map` can turn an array of one type of element (say a -JavaScript object in this case) into an array of another type of element, -( in this case) +In our [lesson on JavaScript iteration](/lessons/js-iteration] we demonstrated +that `map` can turn an array of one type of element (say a JavaScript object in +this case) into an array of another type of element, ( in this +case) So what is happening here is a transformation from the first array to the second. @@ -715,22 +742,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}
-
- ) - } +import articles from './articles.json' + +function App() { + const newsArticlesFromData = articles.map(article => ( + + )) + + return ( +
+
{newsArticlesFromData}
+
+ ) } export default App @@ -749,26 +774,23 @@ 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 our key! +Well, now it is handy that we had that `id` attribute of our JSON objects! ```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}
-
- ) - } +import articles from './articles.json' + +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 78b08df5..0e259f43 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 of JavaScript that allows us to use an HTML-like syntax in o # Simplest React Component ```javascript -class HelloWorld extends React.Component { - render() { - return
Hello, World!
- } +function HelloWorld() { + return

Hello, World!

} ``` @@ -76,8 +82,20 @@ class HelloWorld extends React.Component { # HTML in our JS?!? +--- + +# Transpiling + +```javascript +return

Hello, World!

+``` + +becomes + ```javascript -return
Hello, World!
+function HelloWorld() { + return React.createElement('p', null, 'Hello, World') +} ``` --- @@ -87,11 +105,13 @@ return
Hello, World!
- Our template for React is inspired by `vite` which is a tool for generating web based projects. - React Components can represent the **entire** web page, or be mixed in with static content of the page. ---- +```javascript +return

Hello, World!

+``` # Let's create a new React project -``` +```shell degit $GITHUB_USER/react-project-template ReactArticles ``` @@ -117,7 +137,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.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' @@ -132,7 +152,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. @@ -140,11 +160,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. --- @@ -183,40 +201,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 our `App` ```javascript -import React, { Component } from 'react' - -class App extends Component { - render() { - return ( -
-
- - - -
-
- ) - } +import React from 'react' + +function App() { + return ( +
+
+ + + +
+
+ ) } export default App @@ -313,7 +328,7 @@ import React from 'react' # Next, we will make our component: ```javascript -export class NewsArticle extends React.Component { +export function NewsArticle() { // Code here } ``` @@ -322,15 +337,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
} ``` @@ -338,7 +351,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' @@ -369,22 +382,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 ( + + ) } ``` @@ -401,19 +410,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 ( +
+
+ +
+
+ ) } ``` @@ -424,22 +431,20 @@ class App extends Component { We should only see one article listed. If we repeat the `` we can have as many of the `
` structures 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 @@ -474,9 +479,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 @@ -495,18 +500,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 ( + + ) } ``` @@ -609,22 +612,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 @@ -647,22 +648,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-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/lessons/react-state-with-fetch/index.md b/lessons/react-state-with-fetch/index.md new file mode 100644 index 00000000..0154fa90 --- /dev/null +++ b/lessons/react-state-with-fetch/index.md @@ -0,0 +1,682 @@ +--- +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: number, column: number) { + 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.ok) { + // 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: number, column: number) { + // 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.ok) { + // 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 + +## 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 new file mode 100644 index 00000000..d77ae837 --- /dev/null +++ b/lessons/react-state-with-fetch/lecture.md @@ -0,0 +1,502 @@ +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: number, column: number) { + 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: number, column: number) { + 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.ok) { + // 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: number, column: number) { + // 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.ok) { + // 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 + +--- + +# 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 a1f4162f..82a6c66d 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. +```jsx +function Counter() { + // prettier-ignore + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) -Here is what this looks like for our `Counter` component. We'll review the code -changes below. + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] -```js -export class Counter extends React.Component { - state = { - count: 0, - } - - 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 code initializes an **instance** variable named `state` with a value being -an object containing a single key (`count`) with the value `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`. -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. +### `useState` rules -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. +`useState` has a few particular _rules_ that we need to remember: -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). +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. -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. +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_ -> 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. +### Using the `useState` return value + +Here are the next two lines of code: + +```jsx +const counter = counterValueAndSetMethod[0] +const setCounter = counterValueAndSetMethod[1] +``` -> **Always provide a working default/initial value for `state`** +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` + +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. + +However, this code is not as compact as we can make it! We can use +`array destructuring assignment` to simplify the code. + +The code: + +```jsx +const counterValueAndSetMethod = useState(0) + +const counter = counterValueAndSetMethod[0] +const setCounter = counterValueAndSetMethod[1] +``` + +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`. @@ -214,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 @@ -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 => { +```typescript +function handleClickButton(event: MouseEvent) { 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 - +```typescript // Increment -const newCount = currentCount + 1 - -// Make a new state -const newState = { - count: newCount, -} - -// Tell React about the new state -this.setState(newState) -``` - -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. +const newCounter = counter + 1 -`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() - - // 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}

    - -
    - ) - } -} +// Tell React there is a new value for the count +setCounter(newCounter) ``` -# Simplify the code - -There are some simplifications we could make to `handleClickButton`. +> 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. -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) + setCounter(counter + 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,103 @@ 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; +# A note on types - /* 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; -} +You may have noticed that when declaring these variables we did **not** have to +specify a type: -ul li.small { - font-size: 4rem; -} - -ul li.not-allowed-click { - background-color: red; -} +```typescript +const [counter, setCounter] = useState(0) ``` -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. +However, TypeScript knows that `counter` is an `number` and `setCounter` is a +function that accepts a `number` as an argument. -```js -state = { - board: [ - [' ', ' ', ' '], - [' ', ' ', ' '], - [' ', ' ', ' '], - ], - id: null, - winner: null, -} -``` +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. -We can then update the static representation of our `
  • ` game board: +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` -```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]}
    • -
    -
    - ) -} +```typescript +const [price, setPrice] = useState() ``` -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. +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`. -# Step 3 - Try manually changing the state +In the case where we do **not** provide an initial value to `useState` we +_should_ provide a type. -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 -handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) -} +```typescript +const [price, setPrice] = useState() ``` -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 `
  • ` +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. -```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: +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. -> 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]}
    • -
    -``` +## Adding more state -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! +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. -# Step 5 - Update the state +Separating these pieces of state has a few benefits: -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. +1. It is easier to remove one part of the state since it has its own variable + and state changing function. -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' }, - } - ) +2. We can more easily tell where in the code a piece of state or a state + changing function is used. - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() +```tsx +function CounterWithName() { + const [counter, setCounter] = useState(0) + const [name, setName] = useState('Susan') - // 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: React.ChangeEvent) { + setName(event.target.value) } -} -``` - -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: +# handleChangeInput -```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 +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` -- 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 +# Two independent states -- 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..9a412b8b 100644 --- a/lessons/react-state/lecture.md +++ b/lessons/react-state/lecture.md @@ -30,652 +30,432 @@ 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 + +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. --- -# Step 1 - Static implementation +# We will start with the simplest `hook` in React, `useState`. -- Render a static (hardcoded) version of what you want +`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 { - render() { - return ( -
    -

    The count is 0

    - -
    - ) - } -} -``` +It comes from the standard React library. --- -# Step 2 - Make a state object containing data +# Rules of hooks -- Put a good initial/default value in the state +1. Hooks should all begin with the word `use` and follow `camelCase` names. -```js -export class Counter extends React.Component { - state = { - count: 0, - } - - 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 3 - Try manually changing the value in the state. +# State changes lead to re-rendering -- See that the UI changes when the state is modified +This is a key aspect of `state` in React. -```js -export class Counter extends React.Component { - state = { - count: 42, - } +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. - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` +--- + +# Demo time! + +# [fit] Click Counter + +# [fit] The "Hello World" of interactive web applications! --- -# Step 4 - Connect actions +# A step-by-step approach to building a dynamic UI -- Use the `onXXXX` methods to handle events. +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 -```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() +# Step 1 - Static implementation - console.log('Clicked!') - } +- Render a static (hardcoded) version of what you want - render() { - return ( -
    -

    The count is {this.state.count}

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

    The count is 0

    + +
    + ) } ``` --- -# Now we know we can connect a method to an event. - ---- +# Step 2 - Introduce State -# Step 5 - Update state +Add our first hook, known as `useState`. -- For our button, we want to: - - Get the current count from the state - - Increment it - - Update the state +Here is the code to create the state variables and display their value. We'll then break down this code line-by-line --- -[.column] - ```jsx -export class Counter extends React.Component { - state = { - count: 42, - } +export function Counter() { + // prettier-ignore + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) - handleClickButton = (event) => { - event.preventDefault() + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] - // Get the current count - const currentCount = this.state.count + return ( +
    +

    The counter is {counter}

    + +
    + ) +} +``` - // Increment - const newCount = currentCount + 1 +--- - // Make a new state - const newState = { - count: newCount, - } +[.code-highlight: 2-2] - // Tell React about the new state - this.setState(newState) - } -``` + +```jsx +export function Counter() { + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) -[.column] + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] -```jsx - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` + ---- - -# Simplify the code +- 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 --- -```jsx -export class Counter extends React.Component { - state = { - count: 42, - } +[.autoscale: true] - handleClickButton = event => { - event.preventDefault() +# `useState` rules - const newState = { - count: this.state.count + 1, - } +`useState` has a few particular _rules_ that we need to remember: - this.setState(newState) - } +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. - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } -} -``` +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_ --- -# Even more simple +# Using the `useState` return value ---- +[.code-highlight: 4-6] + ```jsx -export class Counter extends React.Component { - state = { - count: 42, - } +export function Counter() { + const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */) - handleClickButton = event => { - event.preventDefault() + const counter = counterValueAndSetMethod[0] + const setCounter = counterValueAndSetMethod[1] - this.setState({ count: this.state.count + 1 }) - } - - render() { - return ( -
    -

    The count is {this.state.count}

    - -
    - ) - } + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` + ---- +^ Creates two local variables +^ First is the value of the counter +^ Second is a function that allows us to change the counter -# Steps: +--- -- Step 1 - Static implementation +# [fit] Simplify (using Destructuring Assignment) -- Step 2 - Make a state object containing data +```jsx +export function Counter() { + const [counter, setCounter] = useState(0) -- Step 3 - Try manually changing the value in the state. + return ( +
    +

    The counter is {counter}

    + +
    + ) +} +``` -- Step 4 - Connect actions +# [fit] Ah, so much more room for activities... -- Step 5 - Update state +![right fit](https://media.giphy.com/media/yrFrXTTTcHIY0/giphy.gif) --- -# A more complex example: Tic Tac Toe With an API - ---- +# Step 3 - Try manually changing the value in the state. -# Step 1 - Static Implementation +- See that the UI changes when the state is modified ```jsx -export class App extends Component { - render() { - return ( -
    -

    - Tic Tac Toe - -

    -
      -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    • -
    -
    - ) - } +export function Counter() { + const [counter, setCounter] = useState(42) + + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- -# Step 2: Make a state using data +# Step 4 - Connect actions -[.column] +- Create a `handleXXXX` function to handle events. +- We will define this _INSIDE_ our function. Whoa! Nested functions! -- When using an API, taking the data example from the API is a great way to start. +--- -[.column] +```jsx +export function Counter() { + const [counter, setCounter] = useState(42) -```js -{ - "board": [ - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ] - ], - "winner": null, + function handleClickButton(event: MouseEvent) { + event.preventDefault() + + console.log('Clicked!') + } + + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- -# Step 2 Continued: +# [fit] Event handlers still receive **event** object -[.column] +- Except now we **must** provide a specific type. +- Type depends on what _kind_ of handler this is. ```jsx -export class App extends Component { - state = { - board: [ - [' ', ' ', ' '], - [' ', ' ', ' '], - [' ', ' ', ' '], - ], - id: null, - winner: null, - } -``` +function handleClickButton(event: MouseEvent) { + event.preventDefault() -[.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]}
    • -
    -
    - ) - } + console.log('Clicked!') } ``` +Showing how to _prevent the default behavior_, not typically needed outside of links and form submit. + --- -# Step 3: Try manually changing the state +# Connect the event -[.column] +# [fit] Goodbye `addEventListener` -```jsx -export class App extends Component { - state = { - board: [ - ['X', 'O', 'X'], - [' ', ' ', ' '], - [' ', 'O', ' '], - ], - winner: null, - } +```html + ``` -[.column] +- 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. -```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]}
    • -
    -
    - ) - } -} -``` +--- + +# Naming conventions + +- `onXXXXX` or `handleXXXXX` named methods (e.g. `onClick`, `onChange`, `handleClick`, etc.) +- `_buttonClick` -- because the `_` looks like a _"handle"_ attached to the word `buttonClick` --- -# See that this affects the user interface +# [fit] Can connect a method to an event! + +# [fit] Time to update state (finally...) --- -# Step 4: Connect the actions +# Step 5 - Update state -- Define a method that will handle clicking on the cell -- We will need to know the row and column +[.column] -```js -handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) -} -``` +- For our button, we want to: + - Get the current count + - Increment it + - Update the state ---- +[.column] - ```jsx -export class App extends Component { - state = { - board: [ - ['X', 'O', 'X'], - [' ', ' ', ' '], - [' ', 'O', ' '], - ], - winner: null, - } +export function Counter() { + const [counter, setCounter] = useState(42) - handleClickCell = (row, column) => { - console.log(`You clicked on row ${row} and column ${column}`) - } + function handleClickButton(event: MouseEvent) { + event.preventDefault() + + // Increment + const newCount = count + 1 - 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]}
    • -
    -
    - ) + // Tell React there is a new value for the count + setCount(newCount) } + + return ( +
    +

    The counter is {counter}

    + +
    + ) } ``` --- -# Step 5: Update the state +# Warning! Warning! -- For this, we will use the Tic Tac Toe API -- The first step is to make a new game by clicking on `new` +> 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. + +> This often confuses new React developers. We'll see this again when we use more complex state + +![fit right](https://media1.tenor.com/images/fb0638add0828e1975144f3dd93e6e62/tenor.gif?itemid=11779622) --- -```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' }, - } - ) +# Simplify the code - if (response.status === 201) { - // Get the response as JSON - const game = await response.json() +```jsx +function CounterWithName() { + const [counter, setCounter] = useState(0) - // Make that the new state! - this.setState(game) + function handleButtonClick() { + setCounter(counter + 1) } -} -``` -```html -

    Tic Tac Toe -

    + return ( +
    + +
    + ) +} ``` --- -# See that this updates the user interface +# Adding more state -- 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 +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. -[.column] +--- -# Notice the state has some extra information in it now +```jsx +function CounterWithName() { + const [counter, setCounter] = useState(0) + const [name, setName] = useState('Susan') -- We know the _id_ of the game. Useful for future API requests. + function handleButtonClick() { + setCounter(counter + 1) + } -[.column] + function handleChangeInput(event: React.ChangeEvent) { + setName(event.target.value) + } -```js -{ - "id": 5, - "board": [ - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ], - [ - " ", - " ", - " " - ] - ], - "winner": null, - "created_at": "2021-02-19T00:52:49.678Z", - "updated_at": "2021-02-19T00:52:49.678Z" + return ( +
    +

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

    + +

    + +

    +
    + ) } ``` --- -# Now, let us update handleClickCell +# handleChangeInput -```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) - } -} -``` +- 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. --- -# Handle the winner - -- The API gives us information about the winner. -- Let us make the header display the winner +# A note on types --- -# Make the header a string we generate in the code +You may have noticed that when declaring these variables we did **not** have to specify a type: -```jsx -render() { - const header = 'Tic Tac Toe' - - return ( -
    -

    - {header} - -

    +```typescript +const [counter, setCounter] = useState(0) ``` --- -# Now make it dynamic +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` -```js -const header = this.state.winner - ? `${this.state.winner} is the winner` - : 'Tic Tac Toe' +```typescript +const [price, setPrice] = useState() ``` ---- - -# Remove duplication in the creation of the game board - -- 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} -
    • - ) - }) - })} -
    -``` +- 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`. --- -# Block clicks - -- When there is no game -- Or when the user clicks on an occupied cell -- Or when someone has won +In the case where we do **not** provide an initial value to `useState` we _should_ provide a type. -```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 -} +```typescript +const [price, setPrice] = useState() ``` +`price` has a type of `undefined | number`. + --- -# Dynamically set the class name +# Always set default state value -- If the cell is not empty, set the class to `taken` +This is the reason that we **strongly** recommend always using an initial value for all of your `useState` hooks. -```jsx -
  • this.handleClickCell(rowIndex, columnIndex)} -> - {cell} -
  • -``` +If you _cannot_ set an initial value you must be consider the impact that allowing an `undefined` value in a state variable will have. --- @@ -690,9 +470,3 @@ if ( - 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/programs/web-development.yaml b/programs/web-development.yaml index dafc92d6..597c5546 100644 --- a/programs/web-development.yaml +++ b/programs/web-development.yaml @@ -73,7 +73,6 @@ modules: - js-dom - js-event-loop - js-ui-as-state - - js-classes - js-modules - front-end-template-react - react-intro @@ -82,7 +81,6 @@ modules: - js-fetch - react-state - react-state-flow - - react-hooks - react-hooks-use-effect - react-hooks-react-router - full-stack-development: 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; } diff --git a/web/static/lectures/algorithms-day-four-lecture.pdf b/web/static/lectures/algorithms-day-four-lecture.pdf index 1f019c84..cb104919 100644 Binary files a/web/static/lectures/algorithms-day-four-lecture.pdf and b/web/static/lectures/algorithms-day-four-lecture.pdf differ diff --git a/web/static/lectures/algorithms-day-two-lecture.pdf b/web/static/lectures/algorithms-day-two-lecture.pdf index ceb0dec0..db67088e 100644 Binary files a/web/static/lectures/algorithms-day-two-lecture.pdf and b/web/static/lectures/algorithms-day-two-lecture.pdf differ diff --git a/web/static/lectures/algorithms-intro-lecture.pdf b/web/static/lectures/algorithms-intro-lecture.pdf index dd9f9477..e271822e 100644 Binary files a/web/static/lectures/algorithms-intro-lecture.pdf and b/web/static/lectures/algorithms-intro-lecture.pdf differ diff --git a/web/static/lectures/cs-api-clients-lecture.pdf b/web/static/lectures/cs-api-clients-lecture.pdf index d5c1d453..aaff8197 100644 Binary files a/web/static/lectures/cs-api-clients-lecture.pdf and b/web/static/lectures/cs-api-clients-lecture.pdf differ diff --git a/web/static/lectures/cs-api-servers-lecture.pdf b/web/static/lectures/cs-api-servers-lecture.pdf index aad66a0d..c5cf1adb 100644 Binary files a/web/static/lectures/cs-api-servers-lecture.pdf and b/web/static/lectures/cs-api-servers-lecture.pdf differ diff --git a/web/static/lectures/cs-classes-lecture.pdf b/web/static/lectures/cs-classes-lecture.pdf index c916a287..737aff03 100644 Binary files a/web/static/lectures/cs-classes-lecture.pdf and b/web/static/lectures/cs-classes-lecture.pdf differ diff --git a/web/static/lectures/cs-classes-simple-database-lecture.pdf b/web/static/lectures/cs-classes-simple-database-lecture.pdf index 376ae76a..ae82d847 100644 Binary files a/web/static/lectures/cs-classes-simple-database-lecture.pdf and b/web/static/lectures/cs-classes-simple-database-lecture.pdf differ diff --git a/web/static/lectures/cs-files-reading-and-writing-lecture.pdf b/web/static/lectures/cs-files-reading-and-writing-lecture.pdf index 34db1a22..a3a9de2f 100644 Binary files a/web/static/lectures/cs-files-reading-and-writing-lecture.pdf and b/web/static/lectures/cs-files-reading-and-writing-lecture.pdf differ diff --git a/web/static/lectures/cs-how-to-create-and-run-programs-lecture.pdf b/web/static/lectures/cs-how-to-create-and-run-programs-lecture.pdf index cc4132e4..c6ffee9b 100644 Binary files a/web/static/lectures/cs-how-to-create-and-run-programs-lecture.pdf and b/web/static/lectures/cs-how-to-create-and-run-programs-lecture.pdf differ diff --git a/web/static/lectures/cs-methods-lecture.pdf b/web/static/lectures/cs-methods-lecture.pdf index 0d745667..84ed14e4 100644 Binary files a/web/static/lectures/cs-methods-lecture.pdf and b/web/static/lectures/cs-methods-lecture.pdf differ diff --git a/web/static/lectures/cs-object-relational-mapping-lecture.pdf b/web/static/lectures/cs-object-relational-mapping-lecture.pdf index af97c633..47f60744 100644 Binary files a/web/static/lectures/cs-object-relational-mapping-lecture.pdf and b/web/static/lectures/cs-object-relational-mapping-lecture.pdf differ diff --git a/web/static/lectures/css-intro-lecture.pdf b/web/static/lectures/css-intro-lecture.pdf index ab888942..ae221bcc 100644 Binary files a/web/static/lectures/css-intro-lecture.pdf and b/web/static/lectures/css-intro-lecture.pdf differ diff --git a/web/static/lectures/front-end-template-html-css-ts-lecture.pdf b/web/static/lectures/front-end-template-html-css-ts-lecture.pdf index ee274b8f..d739923c 100644 Binary files a/web/static/lectures/front-end-template-html-css-ts-lecture.pdf and b/web/static/lectures/front-end-template-html-css-ts-lecture.pdf differ diff --git a/web/static/lectures/front-end-template-react-lecture.pdf b/web/static/lectures/front-end-template-react-lecture.pdf index 3041ab27..345243a8 100644 Binary files a/web/static/lectures/front-end-template-react-lecture.pdf and b/web/static/lectures/front-end-template-react-lecture.pdf differ diff --git a/web/static/lectures/html-intro-lecture.pdf b/web/static/lectures/html-intro-lecture.pdf index beb84937..7f5cafef 100644 Binary files a/web/static/lectures/html-intro-lecture.pdf and b/web/static/lectures/html-intro-lecture.pdf differ diff --git a/web/static/lectures/js-classes-lecture.pdf b/web/static/lectures/js-classes-lecture.pdf index 549567b8..acb3a859 100644 Binary files a/web/static/lectures/js-classes-lecture.pdf and b/web/static/lectures/js-classes-lecture.pdf differ diff --git a/web/static/lectures/js-dom-lecture.pdf b/web/static/lectures/js-dom-lecture.pdf index 8aff4ff7..e403637f 100644 Binary files a/web/static/lectures/js-dom-lecture.pdf and b/web/static/lectures/js-dom-lecture.pdf differ diff --git a/web/static/lectures/js-enumeration-lecture.pdf b/web/static/lectures/js-enumeration-lecture.pdf index 9f477377..98f240ee 100644 Binary files a/web/static/lectures/js-enumeration-lecture.pdf and b/web/static/lectures/js-enumeration-lecture.pdf differ diff --git a/web/static/lectures/js-intro-lecture.pdf b/web/static/lectures/js-intro-lecture.pdf index 13e04aed..53678152 100644 Binary files a/web/static/lectures/js-intro-lecture.pdf and b/web/static/lectures/js-intro-lecture.pdf differ diff --git a/web/static/lectures/js-modules-lecture.pdf b/web/static/lectures/js-modules-lecture.pdf index e08aa677..63b3eb35 100644 Binary files a/web/static/lectures/js-modules-lecture.pdf and b/web/static/lectures/js-modules-lecture.pdf differ diff --git a/web/static/lectures/js-ui-as-state-lecture.pdf b/web/static/lectures/js-ui-as-state-lecture.pdf index 1cf9c495..1ff771c7 100644 Binary files a/web/static/lectures/js-ui-as-state-lecture.pdf and b/web/static/lectures/js-ui-as-state-lecture.pdf differ diff --git a/web/static/lectures/react-hooks-react-router-lecture.pdf b/web/static/lectures/react-hooks-react-router-lecture.pdf index db80ffbe..11a91834 100644 Binary files a/web/static/lectures/react-hooks-react-router-lecture.pdf and b/web/static/lectures/react-hooks-react-router-lecture.pdf differ diff --git a/web/static/lectures/react-intro-lecture.pdf b/web/static/lectures/react-intro-lecture.pdf index 5b614d70..d616eea4 100644 Binary files a/web/static/lectures/react-intro-lecture.pdf and b/web/static/lectures/react-intro-lecture.pdf differ diff --git a/web/static/lectures/react-state-flow-lecture.pdf b/web/static/lectures/react-state-flow-lecture.pdf index 70ea306e..48ba3e6b 100644 Binary files a/web/static/lectures/react-state-flow-lecture.pdf and b/web/static/lectures/react-state-flow-lecture.pdf differ diff --git a/web/static/lectures/react-state-lecture.pdf b/web/static/lectures/react-state-lecture.pdf index cef73a6b..6228da77 100644 Binary files a/web/static/lectures/react-state-lecture.pdf and b/web/static/lectures/react-state-lecture.pdf differ 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 00000000..20396153 Binary files /dev/null and b/web/static/lectures/react-state-with-fetch-lecture.pdf differ diff --git a/web/static/lectures/sql-erd-lecture.pdf b/web/static/lectures/sql-erd-lecture.pdf index 211355c2..67354809 100644 Binary files a/web/static/lectures/sql-erd-lecture.pdf and b/web/static/lectures/sql-erd-lecture.pdf differ