{"id":20399578,"url":"https://github.com/namonaki0/typescript-tutorial","last_synced_at":"2026-05-09T08:03:15.508Z","repository":{"id":246436039,"uuid":"819624470","full_name":"Namonaki0/typescript-tutorial","owner":"Namonaki0","description":"Complete TypeScript Tutorial","archived":false,"fork":false,"pushed_at":"2024-12-06T23:13:24.000Z","size":72,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-30T13:26:57.790Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Namonaki0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-24T22:18:48.000Z","updated_at":"2024-12-06T23:13:27.000Z","dependencies_parsed_at":"2024-07-30T01:09:14.140Z","dependency_job_id":"e23c37b6-e5c6-4950-8969-e9d8114488fd","html_url":"https://github.com/Namonaki0/typescript-tutorial","commit_stats":null,"previous_names":["namonaki0/typescript-tutorial"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Namonaki0/typescript-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Namonaki0%2Ftypescript-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Namonaki0%2Ftypescript-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Namonaki0%2Ftypescript-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Namonaki0%2Ftypescript-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Namonaki0","download_url":"https://codeload.github.com/Namonaki0/typescript-tutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Namonaki0%2Ftypescript-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32811662,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T04:30:09.423Z","updated_at":"2026-05-09T08:03:15.475Z","avatar_url":"https://github.com/Namonaki0.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"This project stands as an in-depth guide to TypeScript, meticulously covering its fundamental basics and progressing to its more advanced concepts. It starts with basic setup instructions for creating a TypeScript project using Vite and progresses through a series of tutorials covering various TypeScript features and best practices. Key topics include type annotations, type inference, practical applications of type annotation, union types, handling of \"any\", \"unknown\", and \"never\" types, arrays, and objects fundamentals, challenges to reinforce learning, and functions with their complexities.\n\nThe project also delves into advanced TypeScript features such as generics, fetching data with TypeScript, working with the Zod library for data validation, understanding TypeScript declaration files, and class-based programming with TypeScript. Each tutorial is designed to provide hands-on experience with TypeScript, helping learners understand how to apply TypeScript features in real-world scenarios effectively.\n\nOverall, the project is an in-depth TypeScript learning resource, ideal for developers who wish to gain a thorough understanding of TypeScript, from basic to advanced levels, through practical examples and challenges.\n\n## Install\n\n```sh\nnpm create vite@latest typescript -- --template vanilla-ts\n```\n\n## Setup\n\n- create src/tutorial.ts\n- import tutorial in src/main.ts\n\n```ts\nimport './tutorial.ts';\n```\n\n- write code in tutorial\n\n- create README.md\n- copy from final\n\n## Type Annotations\n\nTypeScript Type Annotations allow developers to specify the types of variables, function parameters, return types, and object properties.\n\n```ts\nlet awesomeName: string = 'shakeAndBake';\nawesomeName = 'something';\nawesomeName = awesomeName.toUpperCase();\n// awesomeName = 20;\n\nconsole.log(awesomeName);\n\nlet amount: number = 12;\namount = 12 - 1;\n// amount = 'pants';\n\nlet isAwesome: boolean = true;\nisAwesome = false;\n// isAwesome = 'shakeAndBake';\n```\n\n## Type Inference\n\nThe typescript compiler can infer the type of the variable based on the literal value that is assigned when it is defined. Just make sure you are working in the typescript file 😄\n\n```ts\nlet awesomeName = 'shakeAndBake';\nawesomeName = 'something';\nawesomeName = awesomeName.toUpperCase();\n// awesomeName = 20;\n```\n\n## Challenge\n\n- Create a variable of type string and try to invoke a string method on it.\n- Create a variable of type number and try to perform a mathematical operation on it.\n- Create a variable of type boolean and try to perform a logical operation on it.\n- Try to assign a value of a different type to each of these variables and observe the TypeScript compiler's response.\n- You can use type annotation or inference\n\n```ts\n// 1. String\nlet greeting: string = 'Hello, TypeScript!';\ngreeting = greeting.toUpperCase(); // This should work fine\n\n// 2. Number\nlet age: number = 25;\nage = age + 5; // This should work fine\n\n// 3. Boolean\nlet isAdult: boolean = age \u003e= 18;\nisAdult = !isAdult; // This should work fine\n\n// 4. Assigning different types\n// Uncommenting any of these will result in a TypeScript error\n// greeting = 10; // Error: Type 'number' is not assignable to type 'string'\n// age = 'thirty'; // Error: Type 'string' is not assignable to type 'number'\n// isAdult = 'yes'; // Error: Type 'string' is not assignable to type 'boolean'\n```\n\n## Setup Info\n\n- even with error you can run the project but you won't be able to build it \"npm run build\"\n\n## Union Type\n\nIn TypeScript, a Union Type allows a variable to hold a value of multiple, distinct types, specified using the | operator. It can also be used to specify that a variable can hold one of several specific values. More examples are coming up.\n\n```ts\nlet tax: number | string = 10;\ntax = 100;\ntax = '$10';\n\n// fancy name - literal value type\nlet requestStatus: 'pending' | 'success' | 'error' = 'pending';\nrequestStatus = 'success';\nrequestStatus = 'error';\n// requestStatus = 'random';\n```\n\n## Type - \"any\"\n\nIn TypeScript, the \"any\" type is a powerful way to work with existing JavaScript, allowing you to opt-out of type-checking and let the values pass through compile-time checks. It means a variable declared with the any type can hold a value of any type. Later will also cover - \"unknown\" and \"never\"\n\n```ts\nlet notSure: any = 4;\nnotSure = 'maybe a string instead';\nnotSure = false; // okay, definitely a boolean\n```\n\n## Practical Application of Type Annotation\n\n```ts\nconst books = ['1984', 'Brave New World', 'Fahrenheit 451'];\n\nlet foundBook: string | undefined;\n\nfor (let book of books) {\n  if (book === '1984') {\n    foundBook = book;\n    foundBook = foundBook.toUpperCase();\n    break;\n  }\n}\n\nconsole.log(foundBook?.length);\n```\n\nThe reason to explicitly type foundBook as string | undefined is to make it clear to anyone reading the code (including your future self) that foundBook might be undefined at runtime. This is a good practice in TypeScript because it helps prevent bugs related to undefined values.\n\n## Challenge\n\n- Create a variable orderStatus of type 'processing' | 'shipped' | 'delivered' and assign it the value 'processing'. Then, try to assign it the values 'shipped' and 'delivered'.\n- Create a variable discount of type number | string and assign it the value 20. Then, try to assign it the value '20%'.\n\n```ts\n// 1. Order Status\nlet orderStatus: 'processing' | 'shipped' | 'delivered' = 'processing';\norderStatus = 'shipped';\norderStatus = 'delivered';\n// orderStatus = 'cancelled'; // This will result in a TypeScript error\n\n// 2. Discount\nlet discount: number | string = 20;\ndiscount = '20%';\n// discount = true; // This will result in a TypeScript error\n```\n\n## Arrays - Fundamentals\n\nIn TypeScript, arrays are used to store multiple values in a single variable. You can define the type of elements that an array can hold using type annotations.\n\n```ts\nlet prices: number[] = [100, 75, 42];\n\nlet fruit: string[] = ['apple', 'orange'];\n// fruit.push(1);\n// let people: string[] = ['bobo', 'peter', 1];\n//\n// be careful \"[]\" means literally empty array\n// let randomValues: [] = [1];\n\n// be careful with inferred array types\n// let names = ['peter', 'susan'];\n// let names = ['peter', 'susan', 1];\nlet array: (string | boolean)[] = ['apple', true, 'orange', false];\n```\n\n## Challenge\n\n- Create an array temperatures of type number[] and assign it some values. Then, try to add a string value to it.\n- Create an array colors of type string[] and assign it some values. Then, try to add a boolean value to it.\n- Create an array mixedArray of type (number | string)[] and assign it some values. Then, try to add a boolean value to it.\n\n```ts\n// 1. Temperatures\nlet temperatures: number[] = [20, 25, 30];\n// temperatures.push('hot'); // This will result in a TypeScript error\n\n// 2. Colors\nlet colors: string[] = ['red', 'green', 'blue'];\n// colors.push(true); // This will result in a TypeScript error\n\n// 3. Mixed Array\nlet mixedArray: (number | string)[] = [1, 'two', 3];\n// mixedArray.push(true); // This will result in a TypeScript error\n```\n\n## Objects - Fundamentals\n\nIn TypeScript, an object is a collection of key-value pairs with specified types for each key, providing static type checking for properties.\n\n```ts\nlet car: { brand: string; year: number } = { brand: 'toyota', year: 2020 };\ncar.brand = 'ford';\n// car.color = 'blue';\n\nlet car1: { brand: string; year: number } = { brand: 'audi', year: 2021 };\n// let car2: { brand: string; year: number } = { brand: 'audi' };\n\nlet book = { title: 'book', cost: 20 };\nlet pen = { title: 'pen', cost: 5 };\nlet notebook = { title: 'notebook' };\n\nlet items: { readonly title: string; cost?: number }[] = [book, pen, notebook];\n\nitems[0].title = 'new book'; // Error: Cannot assign to 'title' because it is a read-only property\n```\n\n## Challenge\n\n- Create an object bike of type { brand: string, year: number } and assign it some values. Then, try to assign a string to the year property.\n- Create an object laptop of type { brand: string, year: number } and try to assign an object with missing year property to it.\n- Create an array products of type { title: string, price?: number }[] and assign it some values. Then, try to add an object with a price property of type string to it.\n\n```ts\n// 1. Bike\nlet bike: { brand: string; year: number } = { brand: 'Yamaha', year: 2010 };\n// bike.year = 'old'; // This will result in a TypeScript error\n\n// 2. Laptop\nlet laptop: { brand: string; year: number } = { brand: 'Dell', year: 2020 };\n// let laptop2: { brand: string, year: number } = { brand: 'HP' }; // This will result in a TypeScript error\n\n// 3. Products\nlet product1 = { title: 'Shirt', price: 20 };\nlet product2 = { title: 'Pants' };\nlet products: { title: string; price?: number }[] = [product1, product2];\n// products.push({ title: 'Shoes', price: 'expensive' }); // This will result in a TypeScript error\n```\n\n## Functions - Fundamentals\n\nIn TypeScript, functions can have typed parameters and return values, which provides static type checking and autocompletion support.\n\n```ts\nfunction sayHi(name: string) {\n  console.log(`Hello there ${name.toUpperCase()}!!!`);\n}\n\nsayHi('john');\n// sayHi(3)\n// sayHi('peter', 'random');\n\nfunction calculateDiscount(price: number): number {\n  // price.toUpperCase();\n  const hasDiscount = true;\n  if (hasDiscount) {\n    return price;\n    // return 'Discount Applied';\n  }\n  return price * 0.9;\n}\n\nconst finalPrice = calculateDiscount(200);\nconsole.log(finalPrice);\n\n// \"any\" example\nfunction addThree(number: any) {\n  let anotherNumber: number = 3;\n  return number + anotherNumber;\n}\nconst result = addThree(2);\nconst someValue = result;\n\n// run time error\nsomeValue.myMethod();\n```\n\n## Challenge\n\n- Create a new array of names.\n- Write a new function that checks if a name is in your array. This function should take a name as a parameter and return a boolean.\n- Use this function to check if various names are in your array and log the results.\n\n```ts\nconst names: string[] = ['John', 'Jane', 'Jim', 'Jill'];\n\nfunction isNameInList(name: string): boolean {\n  return names.includes(name);\n}\n\nlet nameToCheck: string = 'Jane';\nif (isNameInList(nameToCheck)) {\n  console.log(`${nameToCheck} is in the list.`);\n} else {\n  console.log(`${nameToCheck} is not in the list.`);\n}\n```\n\n## Functions - Optional and Default Parameters\n\nIn TypeScript, a default parameter value is an alternative to an optional parameter. When you provide a default value for a parameter, you're essentially making it optional because you're specifying a value that the function will use if no argument is provided for that parameter.\n\nHowever, there's a key difference between a parameter with a default value and an optional parameter. If a parameter has a default value, and you call the function without providing an argument for that parameter, the function will use the default value. But if a parameter is optional (indicated with a ?), and you call the function without providing an argument for that parameter, the value of the parameter inside the function will be undefined.\n\n- a function with optional parameters must work when they are not supplied\n\n```ts\nfunction calculatePrice(price: number, discount?: number) {\n  return price - (discount || 0);\n}\n\nlet priceAfterDiscount = calculatePrice(100, 20);\nconsole.log(priceAfterDiscount); // Output: 80\n\nlet priceWithoutDiscount = calculatePrice(300);\nconsole.log(priceWithoutDiscount); // Output: 300\n\nfunction calculateScore(initialScore: number, penaltyPoints: number = 0) {\n  return initialScore - penaltyPoints;\n}\n\nlet scoreAfterPenalty = calculateScore(100, 20);\nconsole.log(scoreAfterPenalty); // Output: 80\n\nlet scoreWithoutPenalty = calculateScore(300);\nconsole.log(scoreWithoutPenalty); // Output: 300\n```\n\n## Function - rest parameter\n\nIn JavaScript, a rest parameter is denoted by three dots (...) before the parameter's name and allows a function to accept any number of arguments. These arguments are collected into an array, which can be accessed within the function.\n\n```ts\nfunction sum(message: string, ...numbers: number[]): string {\n  const doubled = numbers.map((num) =\u003e num * 2);\n  console.log(doubled);\n\n  let total = numbers.reduce((previous, current) =\u003e {\n    return previous + current;\n  }, 0);\n  return `${message} ${total}`;\n}\n\nlet result = sum('The total is:', 1, 2, 3, 4, 5); // result will be \"The total is: 15\"\n```\n\n## Functions - \"void\" return type\n\nIn TypeScript, void is a special type that represents the absence of a value. When used as a function return type, void indicates that the function does not return a value.\n\n```ts\nfunction logMessage(message: string): void {\n  console.log(message);\n}\n\nlogMessage('Hello, TypeScript!'); // Output: Hello, TypeScript!\n```\n\nIt's important to note that in TypeScript, a function that is declared with a void return type can still return a value, but the value will be ignored.For example, the following code is valid TypeScript:\n\n```ts\nfunction logMessage(message: string): void {\n  console.log(message);\n  return 'This value is ignored';\n}\n\nlogMessage('Hello, TypeScript!'); // Output: Hello, TypeScript!\n```\n\n## Functions - Using Union Types as Function Parameters\n\n### Challenge\n\nYour task is to create a function named processInput that accepts a parameter of a union type string | number. The function should behave as follows:\n\n- If the input is of type number, the function should multiply the number by 2 and log the result to the console.\n- If the input is of type string, the function should convert the string to uppercase and log the result to the console.\n\n```ts\nfunction processInput(input: string | number) {\n  if (typeof input === 'number') {\n    console.log(input * 2);\n  } else {\n    console.log(input.toUpperCase());\n  }\n}\n\nprocessInput(10); // Output: 20\nprocessInput('Hello'); // Output: HELLO\n```\n\nIn this example, the processInput function takes a parameter input that can be either a string or a number. Inside the function, we use a type guard (typeof input === 'number') to check the type of input at runtime. If input is a number, we double it. If input is a string, we convert it to uppercase.\n\n## Functions - Using Objects as Function Parameters\n\n```ts\nfunction createEmployee({ id }: { id: number }): {\n  id: number;\n  isActive: boolean;\n} {\n  return { id, isActive: id % 2 === 0 };\n}\n\nconst first = createEmployee({ id: 1 });\nconst second = createEmployee({ id: 2 });\nconsole.log(first, second);\n\n// alternative\nfunction createStudent(student: { id: number; name: string }) {\n  console.log(`Welcome to the course ${student.name.toUpperCase()}!!!`);\n}\n\nconst newStudent = {\n  id: 5,\n  name: 'anna',\n};\n\ncreateStudent(newStudent);\n```\n\n## Gotcha - Excess Property Checks\n\n```ts\nfunction createStudent(student: { id: number; name: string }) {\n  console.log(`Welcome to the course ${student.name.toUpperCase()}!!!`);\n}\n\nconst newStudent = {\n  id: 5,\n  name: 'anna',\n  email: 'anna@gmail.com',\n};\n\ncreateStudent(newStudent);\ncreateStudent({ id: 1, name: 'bob', email: 'bob@gmail.com' });\n```\n\nTypeScript only performs excess property checks on object literals where they're used, not on references to them.\n\nIn TypeScript, when you pass an object literal (like { id: 1, name: 'bob', email: 'bob@gmail.com' }) directly to a function or assign it to a variable with a specified type, TypeScript checks that the object only contains known properties. This is done to catch common errors.\n\nHowever, when you pass newStudent to createStudent, TypeScript doesn't complain about the email property. This is because newStudent is not an object literal at the point where it's passed to createStudent.\n\n## Challenge\n\nYour task is to create a function named processData that accepts two parameters:\n\n- The first parameter, input, should be a union type that can be either a string or a number.\n- The second parameter, config, should be an object with a reverse property of type boolean, by default it \"reverse\" should be false\n\nThe function should behave as follows:\n\n- If input is of type number, the function should return the square of the number.\n- If input is of type string, the function should return the string in uppercase.\n- If the reverse property on the config object is true, and input is a string, the function should return the reversed string in uppercase.\n\n```ts\nfunction processData(\n  input: string | number,\n  config: { reverse: boolean } = { reverse: false }\n): string | number {\n  if (typeof input === 'number') {\n    return input * input;\n  } else {\n    return config.reverse\n      ? input.toUpperCase().split('').reverse().join('')\n      : input.toUpperCase();\n  }\n}\n\nconsole.log(processData(10)); // Output: 100\nconsole.log(processData('Hello')); // Output: HELLO\nconsole.log(processData('Hello', { reverse: true })); // Output: OLLEH\n```\n\n## Type Alias\n\nA type alias in TypeScript is a new name or shorthand for an existing type, making it easier to reuse complex types. However, it's important to note that it doesn't create a new unique type - it's just an alias.All the same rules apply to the aliased type, including the ability to mark properties as optional or readonly.\n\n```ts\nconst john: { id: number; name: string; isActive: boolean } = {\n  id: 1,\n  name: 'john',\n  isActive: true,\n};\nconst susan: { id: number; name: string; isActive: boolean } = {\n  id: 1,\n  name: 'susan',\n  isActive: false,\n};\n\nfunction createUser(user: { id: number; name: string; isActive: boolean }): {\n  id: number;\n  name: string;\n  isActive: boolean;\n} {\n  console.log(`Hello there ${user.name.toUpperCase()} !!!`);\n\n  return user;\n}\n```\n\n```ts\ntype User = { id: number; name: string; isActive: boolean };\n\nconst john: User = {\n  id: 1,\n  name: 'john',\n  isActive: true,\n};\nconst susan: User = {\n  id: 1,\n  name: 'susan',\n  isActive: false,\n};\n\nfunction createUser(user: User): User {\n  console.log(`Hello there ${user.name.toUpperCase()} !!!`);\n  return user;\n}\n\ntype StringOrNumber = string | number; // Type alias for string | number\n\nlet value: StringOrNumber;\nvalue = 'hello'; // This is valid\nvalue = 123; // This is also valid\n\ntype Theme = 'light' | 'dark'; // Type alias for theme\n\nlet theme: Theme;\ntheme = 'light'; // This is valid\ntheme = 'dark'; // This is also valid\n\n// Function that accepts the Theme type alias\nfunction setTheme(t: Theme) {\n  theme = t;\n}\n\nsetTheme('dark'); // This will set the theme to 'dark'\n```\n\n## Challenge\n\n- Define the Employee type: Create a type Employee with properties id (number), name (string), and department (string).\n\n- Define the Manager type: Create a type Manager with properties id (number), name (string), and employees (an array of Employee).\n\n- Create a Union Type: Define a type Staff that is a union of Employee and Manager.\n\n- Create the printStaffDetails function: This function should accept a parameter of type Staff. Inside the function, use a type guard to check if the 'employees' property exists in the passed object. If it does, print a message indicating that the person is a manager and the number of employees they manage. If it doesn't, print a message indicating that the person is an employee and the department they belong to.\n\n- Create Employee and Manager objects: Create two Employee objects. One named alice and second named steve. Also create a Manager object named bob who manages alice and steve.\n\n- Test the function: Call the printStaffDetails function with alice and bob as arguments and verify the output.\n\n```ts\ntype Employee = { id: number; name: string; department: string };\ntype Manager = { id: number; name: string; employees: Employee[] };\n\ntype Staff = Employee | Manager;\n\nfunction printStaffDetails(staff: Staff) {\n  if ('employees' in staff) {\n    console.log(\n      `${staff.name} is a manager of ${staff.employees.length} employees.`\n    );\n  } else {\n    console.log(\n      `${staff.name} is an employee in the ${staff.department} department.`\n    );\n  }\n}\n\nconst alice: Employee = { id: 1, name: 'Alice', department: 'Sales' };\nconst steve: Employee = { id: 1, name: 'Steve', department: 'HR' };\nconst bob: Manager = { id: 2, name: 'Bob', employees: [alice, steve] };\n\nprintStaffDetails(alice); // Outputs: Alice is an employee in the Sales department.\nprintStaffDetails(bob);\n```\n\n## Intersection Types\n\nIn TypeScript, an intersection type (TypeA \u0026 TypeB) is a way of combining multiple types into one. This means that an object of an intersection type will have all the properties of TypeA and all the properties of TypeB. It's a way of creating a new type that merges the properties of existing types.\n\n```ts\ntype Book = { id: number; name: string; price: number };\ntype DiscountedBook = Book \u0026 { discount: number };\nconst book1: Book = {\n  id: 2,\n  name: 'How to Cook a Dragon',\n  price: 15,\n};\n\nconst book2: Book = {\n  id: 3,\n  name: 'The Secret Life of Unicorns',\n  price: 18,\n};\n\nconst discountedBook: DiscountedBook = {\n  id: 4,\n  name: 'Gnomes vs. Goblins: The Ultimate Guide',\n  price: 25,\n  discount: 0.15,\n};\n```\n\n## Type Alias - Computed Properties\n\nComputed properties in JavaScript are a feature that allows you to dynamically create property keys on objects. This is done by wrapping an expression in square brackets [] that computes the property name when creating an object.\n\n```ts\nconst propName = 'age';\n\ntype Animal = {\n  [propName]: number;\n};\n\nlet tiger: Animal = { [propName]: 5 };\n```\n\n## Interface - Fundamentals\n\n- allow to setup shape for objects (only objects)\n\n```ts\ninterface Book {\n  readonly isbn: number;\n  title: string;\n  author: string;\n  genre?: string;\n}\n\nconst deepWork: Book = {\n  isbn: 9781455586691,\n  title: 'Deep Work',\n  author: 'Cal Newport',\n  genre: 'Self-help',\n};\n\ndeepWork.title = 'New Title'; // allowed\n// deepWork.isbn = 654321; // not allowed\n```\n\n## Interface - Methods\n\n```ts\ninterface Book {\n  readonly isbn: number;\n  title: string;\n  author: string;\n  genre?: string;\n  // method\n  printAuthor(): void;\n  printTitle(message: string): string;\n}\n\nconst deepWork: Book = {\n  isbn: 9781455586691,\n  title: 'Deep Work',\n  author: 'Cal Newport',\n  genre: 'Self-help',\n  printAuthor() {\n    console.log(this.author);\n  },\n  printTitle(message) {\n    return `${this.title} ${message}`;\n  },\n};\ndeepWork.printAuthor();\nconst result = deepWork.printTitle('is an awesome book');\nconsole.log(result);\n```\n\n## Interface - Methods (more options)\n\nIt's generally a good practice to match the structure of the interface and the implementing object or class as closely as possible. This makes the code easier to understand and maintain. So, if printAuthor is defined as a method in the Book interface, it would be more consistent to implement it as a method in the deepWork object.\n\n```ts\ninterface Book {\n  readonly isbn: number;\n  title: string;\n  author: string;\n  genre?: string;\n  // method\n  printAuthor(): void;\n  printTitle(message: string): string;\n  printSomething: (someValue: number) =\u003e number;\n}\n\nconst deepWork: Book = {\n  isbn: 9781455586691,\n  title: 'Deep Work',\n  author: 'Cal Newport',\n  genre: 'Self-help',\n  printAuthor() {\n    console.log(this.author);\n  },\n  printTitle(message) {\n    return `${this.title} ${message}`;\n  },\n  // first option\n  // printSomething: function (someValue) {\n  //   return someValue;\n  // },\n  // second option\n  printSomething: (someValue) =\u003e {\n    // \"this\" gotcha\n    console.log(deepWork.author);\n    return someValue;\n  },\n  // third option\n  // printSomething(someValue) {\n  //   return someValue;\n  // },\n  // alternative\n  // printAuthor: () =\u003e {\n  //   console.log(deepWork.author);\n  // },\n};\nconsole.log(deepWork.printSomething(34));\n\ndeepWork.printAuthor();\nconst result = deepWork.printTitle('is an awesome book');\nconsole.log(result);\n```\n\n## Challenge\n\n- Start by defining an interface Computer using the interface keyword. This will serve as a blueprint for objects that will be of this type.\n- Inside the interface, define the properties that the object should have. In this case, we have id, brand, ram, and storage.\n- Use the readonly keyword before the id property to indicate that it cannot be changed once it's set.\n- Use the ? after the storage property to indicate that this property is optional and may not exist on all objects of this type.\n- Also inside the interface, define any methods that the object should have. In this case, we have upgradeRam, which is a function that takes a number and returns a number.\n- Now that we have our interface, we can create an object that adheres to this interface. This object should have all the properties defined in the interface (except for optional ones, which are... optional), and the methods should be implemented.\n- Finally, we can use our object. We can call its upgradeRam method to increase its RAM.\n\n```ts\ninterface Computer {\n  readonly id: number; // cannot be changed once initialized\n  brand: string;\n  ram: number;\n  upgradeRam(increase: number): number;\n  storage?: number; // optional property\n}\n\nconst laptop: Computer = {\n  id: 1,\n  brand: 'random brand',\n  ram: 8, // in GB\n  upgradeRam(amount: number) {\n    this.ram += amount;\n    return this.ram;\n  },\n};\n\nlaptop.storage = 256; // assigning value to optional property\n\nconsole.log(laptop.upgradeRam(4)); // upgrades RAM by 4GB\nconsole.log(laptop);\n```\n\n## Interface - Merging, Extend, TypeGuard\n\n```ts\ninterface Person {\n  name: string;\n  getDetails(): string;\n}\n\ninterface DogOwner {\n  dogName: string;\n  getDogDetails(): string;\n}\n\n// Merging (reopening) an interface in TypeScript is a process where you declare an interface with the same name more than once, and TypeScript will merge their members.\n\n// Merging the interface\ninterface Person {\n  age: number;\n}\n\n// Usage\nconst person: Person = {\n  name: 'John',\n  age: 30,\n  getDetails() {\n    return `Name: ${this.name}, Age: ${this.age}`;\n  },\n};\n\n// Extending an interface in TypeScript is a way to create a new interface that inherits the properties and methods of an existing interface. You use the extends keyword to do this. When you extend an interface, the new interface will have all the members of the base interface, plus any new members that you add.\n\n// Extending the interface\ninterface Employee extends Person {\n  employeeId: number;\n}\n\nconst employee: Employee = {\n  name: 'jane',\n  age: 28,\n  employeeId: 123,\n  getDetails() {\n    return `Name: ${this.name}, Age: ${this.age}, Employee ID: ${this.employeeId}`;\n  },\n};\n\n// Interface multiple inheritance\ninterface Manager extends Person, DogOwner {\n  managePeople(): void;\n}\n\nconst manager: Manager = {\n  name: 'Bob',\n  age: 35,\n  dogName: 'Rex',\n  getDetails() {\n    return `Name: ${this.name}, Age: ${this.age}`;\n  },\n  getDogDetails() {\n    return `Dog Name: ${this.dogName}`;\n  },\n  managePeople() {\n    console.log('Managing people...');\n  },\n};\n```\n\n## Challenge - Part 1\n\n- Define the Person interface Start by defining a Person interface with a name property of type string.\n- Define the DogOwner interface Next, define a DogOwner interface that extends Person and adds a dogName property of type string.\n- Define the Manager interface Then, define a Manager interface that extends Person and adds two methods: managePeople and delegateTasks. Both methods should have a return type of void.\n- Define the getEmployee function Now, define a function called getEmployee that returns a Person, DogOwner, or Manager. Inside this function, generate a random number and use it to decide which type of object to return. If the number is less than 0.33, return a Person. If it's less than 0.66, return a DogOwner. Otherwise, return a Manager.\n- Finally, create a variable called employee that can be a Person, DogOwner, or Manager, and assign it the return value of getEmployee. Then, log employee to the console.\n\n```ts\ninterface Person {\n  name: string;\n}\n\ninterface DogOwner extends Person {\n  dogName: string;\n}\n\ninterface Manager extends Person {\n  managePeople(): void;\n  delegateTasks(): void;\n}\n\nconst employee: Person | DogOwner | Manager = getEmployee();\nconsole.log(employee);\n\nfunction getEmployee(): Person | DogOwner | Manager {\n  const random = Math.random();\n\n  if (random \u003c 0.33) {\n    return {\n      name: 'john',\n    };\n  } else if (random \u003c 0.66) {\n    return {\n      name: 'sarah',\n      dogName: 'Rex',\n    };\n  } else {\n    return {\n      name: 'bob',\n      managePeople: () =\u003e console.log('Managing people...'),\n      delegateTasks: () =\u003e console.log('Delegating tasks...'),\n    };\n  }\n}\n```\n\n## Challenge - Part 2\n\nA type predicate in TypeScript is a special kind of return type for a function that not only returns a boolean, but also asserts that the argument is of a specific type if the function returns true. It's typically used in user-defined type guard functions to narrow down the type of a variable within a certain scope. The syntax is arg is Type, where arg is the function argument and Type is the type you're checking for.\n\n- Define the isManager function Define a function called isManager that takes an object of type Person | DogOwner | Manager and returns a boolean. This function should check if the managePeople method exists on the object, and return true if it does and false if it doesn't. The return type of this function should be a type predicate: obj is Manager.\n- Run your code to see if it works as expected. If employee is a Manager, you should see the output of the delegateTasks method in the console. If employee is a Person or DogOwner, there should be no output.\n\n```ts\n// function isManager(obj: Person | DogOwner | Manager): boolean {\n//   return 'managePeople' in obj;\n// }\n\nfunction isManager(obj: Person | DogOwner | Manager): obj is Manager {\n  return 'managePeople' in obj;\n}\n\nif (isManager(employee)) {\n  employee.delegateTasks();\n}\n```\n\n## Interface vs Type Alias\n\nA type alias is a way to give a name to a type. It can represent primitive types, union types, intersection types, tuples, and any other types. Once defined, the alias can be used anywhere in place of the actual type.\n\n```ts\ntype Person = {\n  name: string;\n  age: number;\n};\n\nlet john: Person = { name: 'John', age: 30 };\n```\n\nInterface\n\nAn interface is a way to define a contract for a certain structure of an object.\n\n```ts\ninterface Person {\n  name: string;\n  age: number;\n}\n\nlet john: Person = { name: 'John', age: 30 };\n```\n\nKey Differences\n\n- Type aliases can represent primitive types, union types, intersection types, tuples, etc., while interfaces are primarily used to represent the shape of an object.\n\n```ts\n// Type alias for a primitive type\ntype Score = number;\ntype NumberOrString = number | string;\n// Type alias for literal types\ntype Direction = 'up' | 'down' | 'left' | 'right';\n\n// Using the type aliases\nlet gameScore: Score = 100;\nlet move: Direction = 'up';\n```\n\n- Interfaces can be merged using declaration merging. If you define an interface with the same name more than once, TypeScript will merge their definitions. Type aliases can't be merged in this way.\n\n- Interfaces can be implemented by classes, while type aliases cannot.\n- Type aliases can use computed properties, while interfaces cannot.\n\n```ts\ninterface Person {\n  name: string;\n  greet(): void;\n}\n\nclass Employee implements Person {\n  name: string;\n\n  constructor(name: string) {\n    this.name = name;\n  }\n\n  greet() {\n    console.log(`Hello, my name is ${this.name}`);\n  }\n}\n\nlet john = new Employee('John');\njohn.greet(); // Outputs: Hello, my name is John\n```\n\n```ts\nconst propName = 'age';\n\ntype Animal = {\n  [propName]: number;\n};\n\nlet tiger: Animal = { [propName]: 5 };\n```\n\n## Tuples\n\nIn TypeScript, a Tuple is a special type that allows you to create an array where the type of a fixed number of elements is known, but need not be the same - in other words it's an array with fixed length and ordered with fixed types. This is useful when you want to group different types of values together.\n\nTuples are useful when you want to return multiple values from a function.\n\nBy default, tuples in TypeScript are not read-only. This means you can modify the values of the elements in the tuple.However, TypeScript does provide a way to make tuples read-only using the readonly keyword.\n\n```ts\nlet person: [string, number] = ['john', 25];\nconsole.log(person[0]); // Outputs: john\nconsole.log(person[1]); // Outputs: 25\n\nlet john: [string, number?] = ['john'];\n\nfunction getPerson(): [string, number] {\n  return ['john', 25];\n}\n\nlet randomPerson = getPerson();\nconsole.log(randomPerson[0]); // Outputs: john\nconsole.log(randomPerson[1]);\n\n// let susan: [string, number] = ['susan', 25];\n// susan[0] = 'bob';\n// susan.push('some random value');\n\nlet susan: readonly [string, number] = ['susan', 25];\n// susan[0] = 'bob';\n// susan.push('some random value');\nconsole.log(susan);\n```\n\n## Enums\n\nEnums in TypeScript allow us to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases.\n\n```ts\nenum ServerResponseStatus {\n  Success = 200,\n  Error = 'Error',\n}\n\ninterface ServerResponse {\n  result: ServerResponseStatus;\n  data: string[];\n}\n\nfunction getServerResponse(): ServerResponse {\n  return {\n    result: ServerResponseStatus.Success,\n    data: ['first item', 'second item'],\n  };\n}\n\nconst response: ServerResponse = getServerResponse();\nconsole.log(response);\n```\n\n## Enums - Gotcha : Reverse Mapping\n\nIn a numeric enum, TypeScript creates a reverse mapping from the numeric values to the enum member names. This means that if you assign a numeric value to an enum member, you can use that numeric value anywhere the enum type is expected.\n\nIn a string enum, TypeScript does not create a reverse mapping. This means that if you assign a string value to an enum member, you cannot use that string value anywhere the enum type is expected. You must use the enum member itself.\n\n```ts\nenum ServerResponseStatus {\n  Success = 'Success',\n  Error = 'Error',\n}\n\nObject.values(ServerResponseStatus).forEach((value) =\u003e {\n  console.log(value);\n});\n```\n\n```ts\nenum ServerResponseStatus {\n  Success = 200,\n  Error = 500,\n}\n\nObject.values(ServerResponseStatus).forEach((value) =\u003e {\n  if (typeof value === 'number') {\n    console.log(value);\n  }\n});\n```\n\n```ts\nenum NumericEnum {\n  Member = 1,\n}\n\nenum StringEnum {\n  Member = 'Value',\n}\n\nlet numericEnumValue: NumericEnum = 1; // This is allowed\nconsole.log(numericEnumValue); // 1\n\nlet stringEnumValue: StringEnum = 'Value'; // This is not allowed\n```\n\n```ts\nenum ServerResponseStatus {\n  Success = 'Success',\n  Error = 'Error',\n}\n\nfunction getServerResponse(): ServerResponse {\n  return {\n    // result: ServerResponseStatus.Success,\n    // this will not fly with string enum but ok with number\n    result: 'Success',\n    data: ['first item', 'second item'],\n  };\n}\n```\n\n## Challenge\n\n- Define an enum named UserRole with members Admin, Manager, and Employee.\n- Define a type alias named User with properties id (number), name (string), role (UserRole), and contact (a tuple with two elements: email as string and phone as string).\n- Define a function named createUser that takes a User object as its parameter and returns a User object.\n- Call the createUser function with an object that matches the User type, store the result in a variable, and log the variable to the console.\n\n```ts\n// Define an enum named UserRole\nenum UserRole {\n  Admin,\n  Manager,\n  Employee,\n}\n\n// Define a type alias named User\ntype User = {\n  id: number;\n  name: string;\n  role: UserRole;\n  contact: [string, string]; // Tuple: [email, phone]\n};\n\n// Define a function named createUser\nfunction createUser(user: User): User {\n  return user;\n}\n\n// Call the createUser function\nconst user: User = createUser({\n  id: 1,\n  name: 'John Doe',\n  role: UserRole.Admin,\n  contact: ['john.doe@example.com', '123-456-7890'],\n});\n\nconsole.log(user);\n```\n\n## Type Assertion\n\nType assertion in TypeScript is a way to tell the compiler what the type of an existing variable is. This is especially useful when you know more about the type of a variable than TypeScript does.\n\n```ts\nlet someValue: any = 'This is a string';\n\n// Using type assertion to treat 'someValue' as a string\nlet strLength: number = (someValue as string).length;\n\ntype Bird = {\n  name: string;\n};\n\n// Assume we have a JSON string from an API or local file\nlet birdString = '{\"name\": \"Eagle\"}';\nlet dogString = '{\"breed\": \"Poodle\"}';\n\n//\n\n// Parse the JSON string into an object\nlet birdObject = JSON.parse(birdString);\nlet dogObject = JSON.parse(dogString);\n\n// We're sure that the jsonObject is actually a Bird\nlet bird = birdObject as Bird;\nlet dog = dogObject as Bird;\n\nconsole.log(bird.name);\nconsole.log(dog.name);\n\nenum Status {\n  Pending = 'pending',\n  Declined = 'declined',\n}\n\ntype User = {\n  name: string;\n  status: Status;\n};\n// save Status.Pending in the DB as a string\n// retrieve string from the DB\nconst statusValue = 'pending';\n\nconst user: User = { name: 'john', status: statusValue as Status };\n```\n\n## Type - 'unknown'\n\nThe unknown type in TypeScript is a type-safe counterpart of the any type. It's like saying that a variable could be anything, but we need to perform some type-checking before we can use it.\n\n\"error instanceof Error\" checks if the error object is an instance of the Error class.\n\n```ts\nlet unknownValue: unknown;\n\n// Assign different types of values to unknownValue\nunknownValue = 'Hello World'; // OK\nunknownValue = [1, 2, 3]; // OK\nunknownValue = 42.3344556; // OK\n\n// unknownValue.toFixed( ); // Error: Object is of type 'unknown'\n\n// Now, let's try to use unknownValue\nif (typeof unknownValue === 'number') {\n  // TypeScript knows that unknownValue is a string in this block\n  console.log(unknownValue.toFixed(2)); // OK\n}\n\nfunction runSomeCode() {\n  const random = Math.random();\n  if (random \u003c 0.5) {\n    throw new Error('Something went wrong');\n  } else {\n    throw 'some error';\n  }\n}\n\ntry {\n  runSomeCode();\n} catch (error) {\n  if (error instanceof Error) {\n    console.log(error.message);\n  } else {\n    console.log(error);\n    console.log('there was an error....');\n  }\n}\n```\n\n## Type - \"never\"\n\nIn TypeScript, never is a type that represents the type of values that never occur.you can't assign any value to a variable of type never.\nTypeScript will give a compile error if there are any unhandled cases, helping ensure that all cases are handled.\n\n```ts\n// let someValue: never = 0;\n\ntype Theme = 'light' | 'dark';\n\nfunction checkTheme(theme: Theme) {\n  if (theme === 'light') {\n    console.log('light theme');\n    return;\n  }\n  if (theme === 'dark') {\n    console.log('dark theme');\n    return;\n  }\n  theme;\n  // theme is of type never, because it can never have a value that is not 'light' or 'dark'.\n}\n```\n\n```ts\nenum Color {\n  Red,\n  Blue,\n  // Green,\n}\n\nfunction getColorName(color: Color) {\n  switch (color) {\n    case Color.Red:\n      return 'Red';\n    case Color.Blue:\n      return 'Blue';\n    default:\n      // at build time\n      let unexpectedColor: never = color;\n      // at runtime\n      throw new Error(`Unexpected color value: ${unexpectedColor}`);\n  }\n}\n\nconsole.log(getColorName(Color.Red)); // Red\nconsole.log(getColorName(Color.Blue)); // Blue\n// console.log(getColorName(Color.Green)); // Green\n```\n\n## Modules - Global Scope \"Gotcha\"\n\nIf your TypeScript files aren't modules (i.e., they don't have any import or export statements), they're treated as scripts in the global scope. In this case, declaring the same variable in two different files would cause a conflict.\n\ntutorial.ts\n\n```ts\nlet name = 'shakeAdnBake';\n\nconst susan = 'susan';\n\nexport let something = 'something';\n```\n\nactions.ts\n\n```ts\nconst susan = 'susan';\n\nexport const something = 'something';\n```\n\ntsconfig.json\n\n```json\n\"moduleDetection\": \"force\",\n```\n\n- output\n\ntsconfig.json\n\n```json\n\"module\": \"ESNext\",\n```\n\n## Modules - Imports/Exports (including types)\n\n```ts\nexport function sayHello(name: string): void {\n  console.log(`Hello ${name}!`);\n}\n\nexport let person = 'susan';\n\nexport type Student = {\n  name: string;\n  age: number;\n};\n\nconst newStudent: Student = {\n  name: 'peter',\n  age: 24,\n};\n\nexport default newStudent;\n```\n\n```ts\nimport newStudent, { sayHello, person, type Student } from './actions';\n\nsayHello('TypeScript');\nconsole.log(person);\nconsole.log(newStudent);\n\nconst anotherStudent: Student = {\n  name: 'bob',\n  age: 23,\n};\n\nconsole.log(anotherStudent);\n```\n\n## Modules - Javascript Files\n\nWhen you set \"allowJs\": true in your tsconfig.json, TypeScript will process JavaScript files and can infer types to a certain extent based on the structure and usage of your JavaScript code.\n\nHowever, TypeScript's ability to infer types from JavaScript is not as robust as when working with TypeScript files. For example, it might not be able to infer complex types or types that depend on runtime behavior.\n\n- create example.js\n- export someValue, import in tutorial\n\n```ts\n  \"allowJs\": true,\n```\n\n## Type Guarding\n\nType guarding is a term in TypeScript that refers to the ability to narrow down the type of an object within a certain scope. This is usually done using conditional statements that check the type of an object.\n\nIn the context of TypeScript, a type guard is some expression that performs a runtime check that guarantees the type in some scope.\n\n## Challenge - \"typeof\" guard\n\n- starter code\n\n```ts\ntype ValueType = string | number | boolean;\n\nlet value: ValueType;\nconst random = Math.random();\nvalue = random \u003c 0.33 ? 'Hello' : random \u003c 0.66 ? 123.456 : true;\n```\n\n- Define the function checkValue that takes one parameter value of type ValueType.\n- Inside the function, use an if statement to check if value is of type string. If it is, log value converted to lowercase and then return from the function.\n- If value is not a string, use another if statement to check if value is of type number. If it is, log value formatted to two decimal places and then return from the function.\n- If value is neither a string nor a number, it must be a boolean. Log the string \"boolean: \" followed by the boolean value.\n- Finally, call the checkValue function with value as the argument.\n\n```ts\nfunction checkValue(value: ValueType) {\n  if (typeof value === 'string') {\n    console.log(value.toLowerCase());\n    return;\n  }\n  if (typeof value === 'number') {\n    console.log(value.toFixed(2));\n    return;\n  }\n  console.log(`boolean: ${value}`);\n}\n\ncheckValue(value);\n```\n\n## Challenge - Equality Narrowing\n\nIn TypeScript, equality narrowing is a form of type narrowing that occurs when you use equality checks like === or !== in your code\n\n- starter code\n\n```ts\ntype Dog = { type: 'dog'; name: string; bark: () =\u003e void };\ntype Cat = { type: 'cat'; name: string; meow: () =\u003e void };\ntype Animal = Dog | Cat;\n```\n\n- Define a function named makeSound that takes one parameter animal of type Animal.\n- Inside the function, use an if statement to check if animal.type is 'dog'.\n- If animal.type is 'dog', TypeScript knows that animal is a Dog in this block. In this case, call the bark method of animal.\n- If animal.type is not 'dog', TypeScript knows that animal is a Cat in the else block. In this case, call the meow method of animal.\n- Now you can call the makeSound function with an Animal as the argument. The function will call the appropriate method (bark or meow) depending on the type of the animal.\n\n```ts\nfunction makeSound(animal: Animal) {\n  if (animal.type === 'dog') {\n    // TypeScript knows that `animal` is a Dog in this block\n    animal.bark();\n  } else {\n    // TypeScript knows that `animal` is a Cat in this block\n    animal.meow();\n  }\n}\n```\n\n## Challenge - check for property\n\nThe \"in\" operator in TypeScript is used to narrow down the type of a variable when used in a conditional statement.It checks if a property or method exists on an object. If it exists, TypeScript will narrow the type to the one that has this property.\n\n- starter code\n\n```ts\ntype Dog = { type: 'dog'; name: string; bark: () =\u003e void };\ntype Cat = { type: 'cat'; name: string; meow: () =\u003e void };\ntype Animal = Dog | Cat;\n```\n\n- Define a function named makeSound that takes one parameter animal of type Animal.\n- Inside the function, use an if statement with the in operator to check if the bark method exists on the animal object.\n- If the bark method exists on animal, TypeScript knows that animal is a Dog in this block. In this case, call the bark method of animal.\n- If the bark method does not exist on animal, TypeScript knows that animal is a Cat in the else block. In this case, call the meow method of animal.\n- Now you can call the makeSound function with an Animal as the argument. The function will call the appropriate method (bark or meow) depending on the type of the animal.\n\n```ts\nfunction makeSound(animal: Animal) {\n  if ('bark' in animal) {\n    // TypeScript knows that `animal` is a Dog in this block\n    animal.bark();\n  } else {\n    // TypeScript knows that `animal` is a Cat in this block\n    animal.meow();\n  }\n}\n```\n\n## Challenge - \"Truthy\"/\"Falsy\" guard\n\nIn TypeScript, \"Truthy\"/\"Falsy\" guard is a simple check for a truthy or falsy value\n\n- Define a function named printLength that takes one parameter str which can be of type string, null, or undefined.\n- Inside the function, use an if statement to check if str is truthy. In JavaScript and TypeScript, a truthy value is a value that is considered true when encountered in a Boolean context. All values are truthy unless they are defined as falsy (i.e., except for false, 0, -0, 0n, \"\", null, undefined, and NaN).\n- If str is truthy, it means it's a string (since null and undefined are falsy). In this case, log the length of str using the length property of the string.\n- If str is not truthy (i.e., it's either null or undefined), log the string 'No string provided'.\n\n- Now you can call the printLength function with a string, null, or undefined as the argument. The function will print the length of the string if a string is provided, or 'No string provided' otherwise.\n\n```ts\nfunction printLength(str: string | null | undefined) {\n  if (str) {\n    // In this block, TypeScript knows that `str` is a string\n    // because `null` and `undefined` are falsy values.\n    console.log(str.length);\n  } else {\n    console.log('No string provided');\n  }\n}\n\nprintLength('Hello'); // Outputs: 5\nprintLength(null); // Outputs: No string provided\nprintLength(undefined); // Outputs: No string provided\n```\n\n## Challenge - \"instanceof\" type guard\n\nThe instanceof type guard is a way in TypeScript to check the specific class or constructor function of an object at runtime. It returns true if the object is an instance of the class or created by the constructor function, and false otherwise.\n\n```ts\ntry {\n  // Some code that may throw an error\n  throw new Error('This is an error');\n} catch (error) {\n  if (error instanceof Error) {\n    console.log('Caught an Error object: ' + error.message);\n  } else {\n    console.log('Caught an unknown error');\n  }\n}\n```\n\n- Start by defining the function using the function keyword followed by the function name, in this case checkInput.\n- Define the function's parameter. The function takes one parameter, input, which can be of type Date or string. This is denoted by input: Date | string.\n- Inside the function, use an if statement to check if the input is an instance of Date. This is done using the instanceof operator.\n- If the input is an instance of Date, return the year part of the date as a string. This is done by calling the getFullYear method on the input and then converting it to a string using the toString method.\n- If the input is not an instance of Date (which means it must be a string), return the input as it is.\n- After defining the function, you can use it by calling it with either a Date or a string as the argument. The function will return the year part of the date if a Date is passed, or the original string if a string is passed.\n- You can store the return value of the function in a variable and then log it to the console to see the result.\n\n```ts\nfunction checkInput(input: Date | string): string {\n  if (input instanceof Date) {\n    return input.getFullYear().toString();\n  }\n  return input;\n}\n\nconst year = checkInput(new Date());\nconst random = checkInput('2020-05-05');\nconsole.log(year);\nconsole.log(random);\n```\n\n## Challenge - Type Predicate\n\nA type predicate is a function whose return type is a special kind of type that can be used to narrow down types within conditional blocks.\n\n- starter code\n\n```ts\ntype Student = {\n  name: string;\n  study: () =\u003e void;\n};\n\ntype User = {\n  name: string;\n  login: () =\u003e void;\n};\n\ntype Person = Student | User;\n\nconst randomPerson = (): Person =\u003e {\n  return Math.random() \u003e 0.5\n    ? { name: 'john', study: () =\u003e console.log('Studying') }\n    : { name: 'mary', login: () =\u003e console.log('Logging in') };\n};\n\nconst person = randomPerson();\n```\n\n- Define the Person and Student types. Student should have a study method and Person should have a login method.\n- Create a function named isStudent that takes a parameter person of type Person.\n- In the function signature, specify a return type that is a type predicate: person is Student.\n- In the function body, use a type assertion to treat person as a Student, and check if the study - method is not undefined. This will return true if person is a Student, and false otherwise.\n- Use the isStudent function in an if statement with person as the argument.\n- In the if block (where isStudent(person) is true), call the study method on person. TypeScript knows that person is a Student in this block, so this is safe.\n- In the else block (where isStudent(person) is false), call the login method on person. This is safe because if person is not a Student, it must be a Person, and all Person objects have a login method.\n\n```ts\nfunction isStudent(person: Person): person is Student {\n  // return 'study' in person;\n  return (person as Student).study !== undefined;\n}\n\n// Usage\n\nif (isStudent(person)) {\n  person.study(); // This is safe because TypeScript knows that 'person' is a Student.\n} else {\n  person.login();\n}\n```\n\n## Optional - type \"never\" gotcha\n\n```ts\ntype Student = {\n  name: string;\n  study: () =\u003e void;\n};\n\ntype User = {\n  name: string;\n  login: () =\u003e void;\n};\n\ntype Person = Student | User;\n\nconst person: Person = {\n  name: 'anna',\n  study: () =\u003e console.log('Studying'),\n  // login: () =\u003e console.log('Logging in'),\n};\n// person;\nfunction isStudent(person: Person): person is Student {\n  // return 'study' in person;\n  return (person as Student).study !== undefined;\n}\n\n// Usage\n\nif (isStudent(person)) {\n  person.study(); // This is safe because TypeScript knows that 'person' is a Student.\n} else {\n  // in this case person is type \"never\"\n  console.log(person);\n}\n```\n\n## Challenge - Discriminated Unions and exhaustive check using the never type\n\nA discriminated union in TypeScript is a type that can be one of several different types, each identified by a unique literal property (the discriminator), allowing for type-safe handling of each possible variant.\n\n- starter code\n\n```ts\ntype IncrementAction = {\n  amount: number;\n  timestamp: number;\n  user: string;\n};\n\ntype DecrementAction = {\n  amount: number;\n  timestamp: number;\n  user: string;\n};\n\ntype Action = IncrementAction | DecrementAction;\n```\n\n- Write a reducer function that takes the current state and an action, and returns the new state. The reducer function should use a switch statement or if-else chain on the type property of the action to handle each action type differently.\n\n- In the default case of the switch statement or the final else clause, perform an exhaustive check by assigning the action to a variable of type never. If there are any action types that haven't been handled, TypeScript will give a compile error.\n\n- Implement the logic for each action type in the reducer function. This typically involves returning a new state based on the current state and the properties of the action.\n\n- Use the reducer function in your application to handle actions and update the state.\n\n```ts\ntype IncrementAction = {\n  type: 'increment';\n  amount: number;\n  timestamp: number;\n  user: string;\n};\n\ntype DecrementAction = {\n  type: 'decrement';\n  amount: number;\n  timestamp: number;\n  user: string;\n};\n\ntype Action = IncrementAction | DecrementAction;\n\nfunction reducer(state: number, action: Action): number {\n  switch (action.type) {\n    case 'increment':\n      return state + action.amount;\n    case 'decrement':\n      return state - action.amount;\n\n    default:\n      const unexpectedAction: never = action;\n      throw new Error(`Unexpected action: ${unexpectedAction}`);\n  }\n}\n\nconst newState = reducer(15, {\n  user: 'john',\n  type: 'increment',\n  amount: 5,\n  timestamp: 123456,\n});\n```\n\n## Generics - Fundamentals\n\nGenerics in TypeScript are a way to create reusable code components that work with a variety of types as opposed to a single one.\n\nIn other words, generics allow you to write a function or a class that can work with any data type. You can think of generics as a kind of variable for types.\n\n```ts\n// In TypeScript, you can declare an array using two syntaxes:\n\n// let array1: string[] = ['Apple', 'Banana', 'Mango'];\n// let array2: number[] = [1, 2, 3];\n// let array3: boolean[] = [true, false, true];\n\nlet array1: Array\u003cstring\u003e = ['Apple', 'Banana', 'Mango'];\nlet array2: Array\u003cnumber\u003e = [1, 2, 3];\nlet array3: Array\u003cboolean\u003e = [true, false, true];\n```\n\n## Generics - Create Generic Function and Interface\n\n```ts\n//\nfunction createString(arg: string): string {\n  return arg;\n}\nfunction createNumber(arg: number): number {\n  return arg;\n}\n\n// Define a generic function\nfunction genericFunction\u003cT\u003e(arg: T): T {\n  return arg;\n}\n\nconst someStringValue = genericFunction\u003cstring\u003e('Hello World');\nconst someNumberValue = genericFunction\u003cnumber\u003e(2);\n\n// Define a generic interface\ninterface GenericInterface\u003cT\u003e {\n  value: T;\n  getValue: () =\u003e T;\n}\n\nconst genericString: GenericInterface\u003cstring\u003e = {\n  value: 'Hello World',\n  getValue() {\n    return this.value;\n  },\n};\n```\n\n## Generics - Promise Example\n\n```ts\n// A Promise in JavaScript (and thus TypeScript) is an object representing the eventual completion or failure of an asynchronous operation.\n\nasync function someFunc(): Promise\u003cstring\u003e {\n  return 'Hello World';\n}\n\nconst result = someFunc();\n```\n\n## Generics - Generate Array\n\n```ts\n// generate an array of strings\nfunction generateStringArray(length: number, value: string): string[] {\n  let result: string[] = [];\n  result = Array(length).fill(value);\n  return result;\n}\n\nconsole.log(generateStringArray(3, 'hello'));\n\nfunction createArray\u003cT\u003e(length: number, value: T): Array\u003cT\u003e {\n  let result: T[] = [];\n  result = Array(length).fill(value);\n  return result;\n}\n\nlet arrayStrings = createArray\u003cstring\u003e(3, 'hello'); // [\"hello\", \"hello\", \"hello\"]\nlet arrayNumbers = createArray\u003cnumber\u003e(4, 100); // [100, 100, 100, 100]\n\nconsole.log(arrayStrings);\nconsole.log(arrayNumbers);\n```\n\n## Generics - Part 5\n\n```ts\nfunction pair\u003cT, U\u003e(param1: T, param2: U): [T, U] {\n  return [param1, param2];\n}\n\n// Usage\nlet result = pair\u003cnumber, string\u003e(123, 'Hello');\nconsole.log(result); // Output: [123, \"Hello\"]\n```\n\n## Generics - Inferred Type and Type Constraints\n\n```ts\nfunction pair\u003cT, U\u003e(param1: T, param2: U): [T, U] {\n  return [param1, param2];\n}\n\n// Usage\nlet result = pair(123, 'Hello');\n\n//  const [name,setName] = useState('')\n//  const [products,setProducts] = useState\u003cProduct[]\u003e([])\n\n// type constraint on the generic type T, generic type can be either a number or a string.\n\nfunction processValue\u003cT extends number | string\u003e(value: T): T {\n  console.log(value);\n}\n\nprocessValue('hello');\nprocessValue(12);\nprocessValue(true);\n```\n\n## Generics - Type Constraints 2\n\n```ts\ntype Car = {\n  brand: string;\n  model: string;\n};\n\nconst car: Car = {\n  brand: 'ford',\n  model: 'mustang',\n};\n\ntype Product = {\n  name: string;\n  price: number;\n};\n\nconst product: Product = {\n  name: 'shoes',\n  price: 1.99,\n};\n\ntype Student = {\n  name: string;\n  age: number;\n};\n\nconst student: Student = {\n  name: 'peter',\n  age: 20,\n};\n\n// T extends Student is a type constraint on the generic type T. It means that the type T can be any type, but it must be a subtype of Student or Student itself. In other words, T must have at least the same properties and methods that Student has.\n\n// function printName\u003cT extends Student\u003e(input: T): void {\n//   console.log(input.name);\n// }\n\n// printName(student);\n\n// function printName\u003cT extends Student | Product\u003e(input: T): void {\n//   console.log(input.name);\n// }\n\n// printName(product);\n\n// The extends { name: string } part is a type constraint on T. It means that T can be any type, but it must be an object that has at least a name property of type string.\n// In other words, T must have at least the same properties and methods that { name: string } has.\nfunction printName\u003cT extends { name: string }\u003e(input: T): void {\n  console.log(input.name);\n}\n\nprintName(student);\nprintName(product);\n```\n\n## Generics - Default Value\n\n```ts\ninterface StoreData\u003cT = any\u003e {\n  data: T[];\n}\n\nconst storeNumbers: StoreData\u003cnumber\u003e = {\n  data: [1, 2, 3, 4],\n};\n\nconst randomStuff: StoreData = {\n  data: ['random', 1],\n};\n```\n\n```ts\n// data is located in the data property\n\nconst { data } = axios.get(someUrl);\n\naxios.get\u003c{ name: string }[]\u003e(someUrl);\n\nexport class Axios {\n  get\u003cT = any, R = AxiosResponse\u003cT\u003e, D = any\u003e(\n    url: string,\n    config?: AxiosRequestConfig\u003cD\u003e\n  ): Promise\u003cR\u003e;\n}\n\nexport interface AxiosResponse\u003cT = any, D = any\u003e {\n  data: T;\n  status: number;\n  statusText: string;\n  headers: RawAxiosResponseHeaders | AxiosResponseHeaders;\n  config: InternalAxiosRequestConfig\u003cD\u003e;\n  request?: any;\n}\n```\n\n## Fetch Data\n\n- typically axios and React Query (or both 🚀🚀🚀)\n- we won't set any state values\n\n```ts\nconst url = 'https://www.course-api.com/react-tours-project';\n\nasync function fetchData(url: string) {\n  try {\n    const response = await fetch(url);\n\n    // Check if the request was successful.\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const data = await response.json();\n    return data;\n  } catch (error) {\n    const errMsg =\n      error instanceof Error ? error.message : 'there was an error...';\n    console.error(errMsg);\n    // throw error;\n    return [];\n  }\n}\n\nconst tours = await fetchData(url);\ntours.map((tour: any) =\u003e {\n  console.log(tour.name);\n});\n\n// return empty array\n// throw error in catch block\n// we are not setting state values in this function\n```\n\n## Challenge - Fetch Data\n\n- setup type and provide correct return type\n\n```ts\nconst url = 'https://www.scourse-api.com/react-tours-project';\n\n// Define a type for the data you're fetching.\ntype Tour = {\n  id: string;\n  name: string;\n  info: string;\n  image: string;\n  price: string;\n  // Add more fields as necessary.\n};\n\nasync function fetchData(url: string): Promise\u003cTour[]\u003e {\n  try {\n    const response = await fetch(url);\n\n    // Check if the request was successful.\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const data: Tour[] = await response.json();\n    console.log(data);\n    return data;\n  } catch (error) {\n    const errMsg =\n      error instanceof Error ? error.message : 'there was an error...';\n    console.error(errMsg);\n\n    // throw error;\n    return [];\n  }\n}\n\nconst tours = await fetchData(url);\ntours.map((tour) =\u003e {\n  console.log(tour.name);\n});\n```\n\n## ZOD Library\n\n- Tour Data \"Gotcha\"\n\n```sh\nnpm install zod\n```\n\n- [Zod](https://zod.dev/)\n- [Error Handling in Zod](https://zod.dev/ERROR_HANDLING)\n\n```ts\nimport { z } from 'zod';\nconst url = 'https://www.course-api.com/react-tours-project';\n\nconst tourSchema = z.object({\n  id: z.string(),\n  name: z.string(),\n  info: z.string(),\n  image: z.string(),\n  price: z.string(),\n  somethign: z.string(),\n});\n\n// extract the inferred type\ntype Tour = z.infer\u003ctypeof tourSchema\u003e;\n\nasync function fetchData(url: string): Promise\u003cTour[]\u003e {\n  try {\n    const response = await fetch(url);\n\n    // Check if the request was successful.\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const rawData: Tour[] = await response.json();\n    const result = tourSchema.array().safeParse(rawData);\n    if (!result.success) {\n      throw new Error(`Invalid data: ${result.error}`);\n    }\n    return result.data;\n  } catch (error) {\n    const errMsg =\n      error instanceof Error ? error.message : 'there was an error...';\n    console.log(errMsg);\n\n    // throw error;\n    return [];\n  }\n}\n\nconst tours = await fetchData(url);\ntours.map((tour) =\u003e {\n  console.log(tour.name);\n});\n```\n\n## Typescript Declaration File\n\nIn TypeScript, .d.ts files, also known as declaration files,consist of type definitions, and are used to provide types for JavaScript code. They allow TypeScript to understand the shape and types of objects, functions, classes, etc., in JavaScript libraries, enabling type checking and autocompletion in TypeScript code that uses these libraries.\n\n- create types.ts\n- export RandomType\n\ntsconfig.json\n\n- `lib`: Set to `[\"ES2020\", \"DOM\", \"DOM.Iterable\"]`. This specifies the library files to be included in the compilation.\n  Specify a set of bundled library declaration files that describe the target runtime environment.\n\n- libraries\n\n[DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped)\n\n- password hashing library\n\n```sh\nnpm i bcryptjs\n```\n\n```sh\nnpm install --save-dev @types/bcryptjs\n```\n\n# tsconfig.json Configuration\n\n[tsconfig](https://www.typescriptlang.org/tsconfig)\n\nThis project's TypeScript configuration is defined in the `tsconfig.json` file. Here's a breakdown of the configuration options:\n\n- `include`: Set to `[\"src\"]`. This tells TypeScript to only convert files in the `src` directory.\n\n- `target`: Set to `ES2020`. This is the JavaScript version that the TypeScript code will be compiled to.\n\n- `useDefineForClassFields`: Set to `true`. This enables the use of the `define` semantics for initializing class fields.\n\n- `module`: Set to `ESNext`. This is the module system for the compiled code.\n\n- `lib`: Set to `[\"ES2020\", \"DOM\", \"DOM.Iterable\"]`. This specifies the library files to be included in the compilation.\n\n- `skipLibCheck`: Set to `true`. This makes TypeScript skip type checking of declaration files (`*.d.ts`).\n\n- `moduleResolution`: Set to `bundler`. This sets the strategy TypeScript uses to resolve modules.\n\n- `allowImportingTsExtensions`: Set to `true`. This allows importing of TypeScript files from JavaScript files.\n\n- `resolveJsonModule`: Set to `true`. This allows importing of `.json` modules from TypeScript files.\n\n- `isolatedModules`: Set to `true`. This ensures that each file can be safely transpiled without relying on other import/export files.\n\n- `noEmit`: Set to `true`. This tells TypeScript to not emit any output files (`*.js` and `*.d.ts` files) after compilation.\n\n- `strict`: Set to `true`. This enables all strict type-checking options.\n\n- `noUnusedLocals`: Set to `true`. This reports an error when local variables are declared but never used.\n\n- `noUnusedParameters`: Set to `true`. This reports an error when function parameters are declared but never used.\n\n- `noFallthroughCasesInSwitch`: Set to `true`. This reports an error for fall through cases in switch statements.\n\n## Classes - Intro\n\nClasses in JavaScript are a blueprint for creating objects. They encapsulate data with code to manipulate that data. Classes in JavaScript support inheritance and can be used to create more complex data structures.\n\nA constructor in a class is a special method that gets called when you create a new instance of the class. It's often used to set the initial state of the object.\n\n```ts\nclass Book {\n  title: string;\n  author: string;\n  constructor(title: string, author: string) {\n    this.title = title;\n    this.author = author;\n  }\n}\n\nconst deepWork = new Book('deep work ', 'cal newport');\n```\n\n## Classes - Instance Property / Default Property\n\nThe checkedOut property in Book class is an instance property (or member variable). It's not specifically set in the constructor, so it could also be referred to as a default property or a property with a default value.\n\n```ts\nclass Book {\n  title: string;\n  author: string;\n  checkedOut: boolean = false;\n  constructor(title: string, author: string) {\n    this.title = title;\n    this.author = author;\n  }\n}\n\nconst deepWork = new Book('deep work ', 'cal newport');\ndeepWork.checkedOut = true;\n// deepWork.checkedOut = 'something else';\n```\n\n## Classes - ReadOnly Modifier\n\n- readonly modifier\n\n```ts\nclass Book {\n  readonly title: string;\n  author: string;\n  checkedOut: boolean = false;\n  constructor(title: string, author: string) {\n    this.title = title;\n    this.author = author;\n  }\n}\n\nconst deepWork = new Book('deep work ', 'cal newport');\n\ndeepWork.title = 'something else';\n```\n\n## Classes - Private and Public Modifiers\n\n- private and public modifiers\n\n```ts\nclass Book {\n  public readonly title: string;\n  public author: string;\n  private checkedOut: boolean = false;\n  constructor(title: string, author: string) {\n    this.title = title;\n    this.author = author;\n  }\n  public checkOut() {\n    this.checkedOut = this.toggleCheckedOutStatus();\n  }\n  public isCheckedOut() {\n    return this.checkedOut;\n  }\n  private toggleCheckedOutStatus() {\n    return !this.checkedOut;\n  }\n}\n\nconst deepWork = new Book('Deep Work', 'Cal Newport');\ndeepWork.checkOut();\nconsole.log(deepWork.isCheckedOut()); // true\n// deepWork.toggleCheckedOutStatus(); // Error: Property 'toggleCheckedOutStatus' is private and only accessible within class 'Book'.\n```\n\n## Classes - Shorthand Syntax\n\nIn TypeScript, if you want to use the shorthand for creating and initializing class properties in the constructor, you need to use public, private, or protected access modifiers.\n\n```ts\nclass Book {\n  private checkedOut: boolean = false;\n  constructor(public readonly title: string, public author: string) {}\n}\n```\n\n## Classes - Getters and Setters\n\nGetters and setters are special methods in a class that allow you to control how a property is accessed and modified.They are used like properties, not methods, so you don't use parentheses to call them.\n\n```ts\nclass Book {\n  private checkedOut: boolean = false;\n  constructor(public readonly title: string, public author: string) {}\n  get info() {\n    return `${this.title} by ${this.author}`;\n  }\n\n  private set checkOut(checkedOut: boolean) {\n    this.checkedOut = checkedOut;\n  }\n  get checkOut() {\n    return this.checkedOut;\n  }\n  public get someInfo() {\n    this.checkOut = true;\n    return `${this.title} by ${this.author}`;\n  }\n}\n\nconst deepWork = new Book('deep work', 'cal newport');\nconsole.log(deepWork.info);\n// deepWork.checkOut = true;\nconsole.log(deepWork.someInfo);\nconsole.log(deepWork.checkOut);\n```\n\n## Classes - Implement Interface\n\nIn TypeScript, an interface is a way to define a contract for a certain structure of an object. This contract can then be used by a class to ensure it adheres to the structure defined by the interface.\n\nWhen a class implements an interface, it is essentially promising that it will provide all the properties and methods defined in the interface. If it does not, TypeScript will throw an error at compile time.\n\n```ts\ninterface IPerson {\n  name: string;\n  age: number;\n  greet(): void;\n}\n\nclass Person implements IPerson {\n  constructor(public name: string, public age: number) {}\n\n  greet() {\n    console.log(\n      `Hello, my name is ${this.name} and I'm ${this.age} years old.`\n    );\n  }\n}\n\nconst hipster = new Person('shakeAndBake', 100);\nhipster.greet();\n```\n\n## Tasks - Setup\n\n- create tasks.html (root) and src/tasks.ts\n- optional : change href in main.ts\n- optional css\n  - create tasks.css (copy from final or end of the README)\n  - setup link in tasks.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003ctitle\u003eTasks\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href=\"src/tasks.css\" /\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cmain\u003e\n      \u003ch2\u003eTasks\u003c/h2\u003e\n      \u003cform class=\"form\"\u003e\n        \u003cinput type=\"text\" class=\"form-input\" /\u003e\n        \u003cbutton type=\"submit\" class=\"btn\"\u003eadd task\u003c/button\u003e\n      \u003c/form\u003e\n      \u003cul class=\"list\"\u003e\u003c/ul\u003e\n      \u003cbutton class=\"test-btn\"\u003eclick me\u003c/button\u003e\n    \u003c/main\u003e\n    \u003cscript type=\"module\" src=\"src/tasks.ts\"\u003e\u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Tasks - Part 2\n\n```ts\nconst btn = document.querySelector('.btn');\n\nbtn?.addEventListener('click', () =\u003e {\n  console.log('something');\n});\n\nif (btn) {\n  // do something\n}\n```\n\nThe ! operator in TypeScript is officially known as the Non-null assertion operator. It is used to assert that its preceding expression is not null or undefined.\n\n```ts\nconst btn = document.querySelector('.btn')!;\n\nbtn.addEventListener('click', () =\u003e {\n  console.log('something');\n});\n```\n\nElement is the most general base class from which all element objects (i.e. objects that represent elements) in a Document inherit. It only has methods and properties common to all kinds of elements. More specific classes inherit from Element.\n\n```ts\nconst btn = document.querySelector\u003cHTMLButtonElement\u003e('.selector')!;\n\nbtn.disabled = true;\n\nconst btn = document.querySelector('.selector')! as HTMLButtonElement;\n\nbtn.disabled = true;\n```\n\n## Tasks - Part 3\n\n```ts\nconst taskForm = document.querySelector\u003cHTMLFormElement\u003e('.form');\nconst formInput = document.querySelector\u003cHTMLInputElement\u003e('.form-input');\nconst taskListElement = document.querySelector\u003cHTMLUListElement\u003e('.list');\n\n// task type\ntype Task = {\n  description: string;\n  isCompleted: boolean;\n};\n\nconst tasks: Task[] = [];\n```\n\n## Tasks - Part 4\n\n```ts\ntaskForm?.addEventListener('submit', (event) =\u003e {\n  event.preventDefault();\n  const taskDescription = formInput?.value;\n  if (taskDescription) {\n    // add task to list\n    // render tasks\n    // update local storage\n\n    formInput.value = '';\n    return;\n  }\n  alert('Please enter a task description');\n});\n```\n\n- event gotcha\n\n```ts\nfunction createTask(event: SubmitEvent) {\n  event.preventDefault();\n  const taskDescription = formInput?.value;\n  if (taskDescription) {\n    // add task to list\n    // render tasks\n    // update local storage\n\n    formInput.value = '';\n    return;\n  }\n  alert('Please enter a task description');\n}\n\ntaskForm?.addEventListener('submit', createTask);\n```\n\n## Tasks - Part 5\n\n```ts\ntaskForm?.addEventListener('submit', (event) =\u003e {\n  event.preventDefault();\n  const taskDescription = formInput?.value;\n  if (taskDescription) {\n    const task: Task = {\n      description: taskDescription,\n      isCompleted: false,\n    };\n    // add task to list\n    addTask(task);\n    // render tasks\n\n    // update local storage\n\n    formInput.value = '';\n    return;\n  }\n  alert('Please enter a task description');\n});\n\nfunction addTask(task: Task): void {\n  tasks.push(task);\n  // console.log(tasks);\n}\n```\n\n## Tasks - Part 6\n\n```ts\nfunction renderTask(task: Task): void {\n  const taskElement = document.createElement('li');\n  taskElement.textContent = task.description;\n  taskListElement?.appendChild(taskElement);\n}\n```\n\n```ts\n// add task to list\naddTask(task);\n// render task\nrenderTask(task);\n```\n\n## Tasks - Part 7\n\n```ts\n// Retrieve tasks from localStorage\nconst tasks: Task[] = loadTasks();\n\n// Load tasks from localStorage\nfunction loadTasks(): Task[] {\n  const storedTasks = localStorage.getItem('tasks');\n  return storedTasks ? JSON.parse(storedTasks) : [];\n}\n\n// tasks.forEach((task) =\u003e renderTask(task));\ntasks.forEach(renderTask);\n\n// Update tasks in localStorage\nfunction updateStorage(): void {\n  localStorage.setItem('tasks', JSON.stringify(tasks));\n}\n```\n\n```ts\n// add task to list\naddTask(task);\n// render task\nrenderTask(task);\n// update local storage\nupdateStorage();\n```\n\n## Tasks - Part 8\n\n```ts\nfunction renderTask(task: Task): void {\n  const taskElement = document.createElement('li');\n  taskElement.textContent = task.description;\n  // checkbox\n  const taskCheckbox = document.createElement('input');\n  taskCheckbox.type = 'checkbox';\n  taskCheckbox.checked = task.isCompleted;\n\n  taskElement.appendChild(taskCheckbox);\n  taskListElement?.appendChild(taskElement);\n}\n```\n\n## Tasks - Part 9\n\n```ts\nfunction renderTask(task: Task): void {\n  const taskElement = document.createElement('li');\n  taskElement.textContent = task.description;\n  // checkbox\n  const taskCheckbox = document.createElement('input');\n  taskCheckbox.type = 'checkbox';\n  taskCheckbox.checked = task.isCompleted;\n  // toggle checkbox\n  taskCheckbox.addEventListener('change', () =\u003e {\n    task.isCompleted = !task.isCompleted;\n    updateStorage();\n  });\n\n  taskElement.appendChild(taskCheckbox);\n  taskListElement?.appendChild(taskElement);\n}\n```\n\n## Tasks - CSS\n\ntasks.css\n\n```css\n/* ============= GLOBAL CSS =============== */\n\n*,\n::after,\n::before {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\nhtml {\n  font-size: 100%;\n} /*16px*/\n\n:root {\n  /* colors */\n  --primary-100: #e2e0ff;\n  --primary-200: #c1beff;\n  --primary-300: #a29dff;\n  --primary-400: #837dff;\n  --primary-500: #645cff;\n  --primary-600: #504acc;\n  --primary-700: #3c3799;\n  --primary-800: #282566;\n  --primary-900: #141233;\n\n  /* grey */\n  --grey-50: #f8fafc;\n  --grey-100: #f1f5f9;\n  --grey-200: #e2e8f0;\n  --grey-300: #cbd5e1;\n  --grey-400: #94a3b8;\n  --grey-500: #64748b;\n  --grey-600: #475569;\n  --grey-700: #334155;\n  --grey-800: #1e293b;\n  --grey-900: #0f172a;\n  /* rest of the colors */\n  --black: #222;\n  --white: #fff;\n  --red-light: #f8d7da;\n  --red-dark: #842029;\n  --green-light: #d1e7dd;\n  --green-dark: #0f5132;\n\n  --small-text: 0.875rem;\n  --extra-small-text: 0.7em;\n  /* rest of the vars */\n\n  --border-radius: 0.25rem;\n  --letter-spacing: 1px;\n  --transition: 0.3s ease-in-out all;\n  --max-width: 1120px;\n  --fixed-width: 600px;\n  --view-width: 90vw;\n  /* box shadow*/\n  --shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);\n  --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n  --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\n  --shadow-4: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n  /* DARK MODE */\n  --background-color: var(--grey-50);\n  --text-color: var(--grey-900);\n}\n\nbody {\n  background: var(--background-color);\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n    Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  font-weight: 400;\n  line-height: 1;\n  color: var(--text-color);\n}\np {\n  margin: 0;\n}\nh1,\nh2,\nh3,\nh4,\nh5 {\n  margin: 0;\n  font-weight: 400;\n  line-height: 1;\n  text-transform: capitalize;\n  letter-spacing: var(--letter-spacing);\n}\n\nh1 {\n  font-size: clamp(2rem, 5vw, 5rem); /* Large heading */\n}\n\nh2 {\n  font-size: clamp(1.5rem, 3vw, 3rem); /* Medium heading */\n}\n\nh3 {\n  font-size: clamp(1.25rem, 2.5vw, 2.5rem); /* Small heading */\n}\n\nh4 {\n  font-size: clamp(1rem, 2vw, 2rem); /* Extra small heading */\n}\n\nh5 {\n  font-size: clamp(0.875rem, 1.5vw, 1.5rem); /* Tiny heading */\n}\n\nsmall,\n.text-small {\n  font-size: var(--small-text);\n}\n\na {\n  text-decoration: none;\n}\nul {\n  list-style-type: none;\n  padding: 0;\n}\n\n.img {\n  width: 100%;\n  display: block;\n  object-fit: cover;\n}\n/* buttons */\n\n.btn {\n  cursor: pointer;\n  color: var(--white);\n  background: var(--primary-500);\n  border: transparent;\n  letter-spacing: var(--letter-spacing);\n  box-shadow: var(--shadow-1);\n  transition: var(--transition);\n  text-transform: capitalize;\n  display: inline-block;\n}\n.btn:hover {\n  background: var(--primary-700);\n  box-shadow: var(--shadow-3);\n}\n.btn-hipster {\n  color: var(--primary-500);\n  background: var(--primary-200);\n}\n.btn-hipster:hover {\n  color: var(--primary-200);\n  background: var(--primary-700);\n}\n.btn-block {\n  width: 100%;\n}\n\n/* alerts */\n.alert {\n  padding: 0.375rem 0.75rem;\n  margin-bottom: 1rem;\n  border-color: transparent;\n  border-radius: var(--border-radius);\n}\n\n.alert-danger {\n  color: var(--red-dark);\n  background: var(--red-light);\n}\n.alert-success {\n  color: var(--green-dark);\n  background: var(--green-light);\n}\n/* form */\n\n.form {\n  background: var(--white);\n  border-radius: var(--border-radius);\n  box-shadow: var(--shadow-2);\n  padding: 2rem 2.5rem;\n  margin-bottom: 2rem;\n}\n\n.form-input {\n  width: 100%;\n  padding: 0.375rem 0.75rem;\n  border-top-left-radius: var(--border-radius);\n  border-bottom-left-radius: var(--border-radius);\n  background: var(--background-color);\n  border: 1px solid var(--grey-200);\n}\n\nmain {\n  padding: 5rem 0;\n  min-height: 100vh;\n  width: 90vw;\n  max-width: 500px;\n  margin: 0 auto;\n}\n\n/* title */\nmain h2 {\n  text-align: center;\n  margin-bottom: 2rem;\n}\n\n.form {\n  display: grid;\n  grid-template-columns: 1fr 100px;\n}\n\n.form button {\n  border-top-right-radius: var(--border-radius);\n  border-bottom-right-radius: var(--border-radius);\n}\n\n.list li {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 0.75rem 1.25rem;\n  margin-bottom: 0.5rem;\n  background: var(--white);\n  border-radius: var(--border-radius);\n  box-shadow: var(--shadow-1);\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamonaki0%2Ftypescript-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnamonaki0%2Ftypescript-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamonaki0%2Ftypescript-tutorial/lists"}