# Working with TypeScript
## 3.1 How to work with native classes and types related to the Window object in the browser?
### IntersectionObserver
```typescript
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is in view!');
}
});
});
observer.observe(document.querySelector('#someElement'));
//Error: Argument of type 'Element | null' is not assignable to parameter of type 'Element'. Type 'null' is not assignable to type 'Element'.
//solution:
const observer: IntersectionObserver = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
console.log('Element is in view!');
}
});
});
observer.observe(document.querySelector('#someElement')!);
```
### Local Storage
```typescript
const userSettings = localStorage.getItem('userSettings');
console.log(JSON.parse(userSettings));
//error: Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'
//solution
const userSettings = localStorage.getItem('userSettings');
if (userSettings !== null) {
console.log(JSON.parse(userSettings)); // Safely parsing, knowing it's not null
} else {
console.log("No user settings found.");
}
```
### Posibly return null value when select element
```typescript
const myCanvas = document.querySelector('#myCanvas');
console.log(myCanvas.getContext('2d')); //Error: 'myCanvas' is possibly 'null'
//=> can't call the function getContext()
//solution:
const myCanvas = document.querySelector('#myCanvas') as HTMLCanvasElement;
console.log(myCanvas.getContext('2d')); // TypeScript knows myCanvas is a canvas
```
### DOM manipulation
#### Traversing the DOM
```htmlembedded
<div>
<ul id="main">
<li>hello</li>
<li>world</li>
</ul>
</div>
```
```typescript
// Selecting an element
const myList = document.getElementById('main') as HTMLUListElement;
// Accessing children
const listItems = myList.children; // HTMLCollection
console.log("listItems", listItems)
// Accessing the first child
const firstItem = myList.firstElementChild as HTMLLIElement;
console.log("firstItem", firstItem)
// Navigating to siblings
const secondItem = firstItem.nextElementSibling as HTMLLIElement;
console.log("secondItem", secondItem)
// Accessing the parent node
const listParent = secondItem.parentElement as HTMLElement;
console.log("listParent", listParent)
```
#### Creating and Appending Elements
```typescript
// Creating a new list item
const newItem = document.createElement('li') as HTMLLIElement;
newItem.textContent = 'New Item';
// Appending the new item to the list
myList.appendChild(newItem);
```
Example: create dynamic list
```typescript
const addItemToList = (itemText: string): void =>{
const list = document.getElementById('main') as HTMLUListElement;
const newItem = document.createElement('li') as HTMLLIElement;
newItem.textContent = itemText;
list.appendChild(newItem);
}
// Example usage
addItemToList('Learn TypeScript');
```
## 3.2 How to assign dynamic values to the Window object?
### 3.2.1 Direct Extension Using declare global
```typescript
// In a TypeScript module (.ts file)
declare global {
interface Window {
customMethod: () => void;
}
}
// Implementing the custom method
window.customMethod = () => console.log("This is a custom method.");
// Using the custom method
window.customMethod();
```
### 3.2.2 Extending Window with a Custom Interface
```typescript
// Defining a custom interface that extends the original Window interface
interface CustomWindow extends Window {
userToken?: string; // Example of a custom property
}
// Casting the global window object to the type of your custom interface
declare let window: CustomWindow;
// Setting a dynamic value
window.userToken = 'abc123';
// Accessing the custom property
console.log(window.userToken); // Outputs: abc123
```
### More example
```typescript
// Method 1: Direct Extension Using declare global
// Use Case: Custom Error Logging
// Implementing a global error logging mechanism that can capture and report errors across the entire application.
//
// Extending the window object for global error logging
declare global {
interface Window {
logError: (error: Error, context?: string) => void;
}
}
// Implementation of the global error logging function
window.logError = (error, context = "General") => {
console.log(`Error in ${context}:`, error);
// Here, you'd typically send the error information to a monitoring service
};
// Global error handler
window.onerror = (message, source, lineno, colno, error) => {
window.logError(error || new Error(message.toString()), "Unhandled Exception");
return true; // Prevents the default handling of the error
};
// Example of using the logging function directly
try {
// Some error-prone operation
throw new Error('Something went wrong');
} catch (error) {
window.logError(error, "Example Function");
}
//Method 2: Extending Window with a Custom Interface
// Use Case: Global State Management
// In applications that don't use state management libraries or frameworks, it can be useful to attach a global state object to the window for easy access across different parts of the application, especially for small to medium-sized project
// Defining an interface for the global state
interface CustomWindow extends Window {
appState: {
userLoggedIn: boolean;
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
};
}
// Casting the global window object to the type of the custom interface
declare let window: CustomWindow;
// Initializing the global state
window.appState = {
userLoggedIn: false,
theme: 'light',
setTheme: function(theme: 'light' | 'dark') {
this.theme = theme;
// Additional logic to apply the theme across the application
console.log(`Theme set to ${theme}`);
},
};
// Example usage
window.appState.setTheme('dark');
console.log(`User logged in: ${window.appState.userLoggedIn}`);
```
## 3.3 How to go through the DOM objects using TypeScript
### 3.3.1. Type Assertions
Common Elements Requiring Type Assertions
- Input Elements (HTMLInputElement): For accessing value, checked.
- Select Elements (HTMLSelectElement): To work with options, selectedIndex.
- TextArea Elements (HTMLTextAreaElement): To manipulate value.
- Button Elements (HTMLButtonElement): When accessing button-specific attributes.
Recognizing When to Use Type Assertions
- The element has properties or methods not present on the generic HTMLElement.
- You're accessing form fields or media elements with specific properties (like value for inputs or play for audio/video).
```typescript
//example 1:
const myInput = document.getElementById("myInput");
console.log(myInput.value); // Error: Property 'value' does not exist on type 'HTMLElement | null'.
//solution
const myInput = document.getElementById("myInput") as HTMLInputElement;
console.log(myInput.value); // Correctly accesses 'value'.
//example 2:
const myAudio = document.getElementById("myAudio");
myAudio.play(); // Error: Property 'play' does not exist on type 'HTMLElement | null'.
//solution
const myAudio = document.getElementById("myAudio") as HTMLAudioElement;
myAudio.play(); // Correctly accesses 'play'.
```
### 3.3.2. Runtime Type Checking
TypeScript can't guarantee the actual type of an element at runtime. Therefore, checking an element's type before performing operations is crucial for avoiding errors.
```typescript
//example 1:
const myCanvas = document.getElementById("myCanvas");
const ctx = myCanvas.getContext("2d"); // Error: 'getContext' does not exist on type 'HTMLElement | null'.
//solution
const myCanvas = document.getElementById("myCanvas");
if (myCanvas instanceof HTMLCanvasElement) {
const ctx = myCanvas.getContext("2d");
// Safe to use 'getContext'
}
//example 2:
const myVideo = document.getElementById("myVideo");
myVideo.play(); // Error: 'play' does not exist on type 'HTMLElement | null'.
//solution
const myVideo = document.getElementById("myVideo");
if (myVideo instanceof HTMLVideoElement) {
myVideo.play(); // Safe to use 'play'
}
```
### 3.3.3. Handling null and undefinedDOM
```typescript
//example 1:
const myDiv = document.getElementById("myDiv");
myDiv.textContent = "Hello World"; // Error: Object is possibly 'null'.
//solution
const myDiv = document.getElementById("myDiv");
if (myDiv) {
myDiv.textContent = "Hello World"; // Safely updates the text.
}
//or
const myDiv = document.getElementById("myDiv");
myDiv?.textContent = "Hello World"; // Safely updates the text if myDiv is not null.
//example 2:
const myButton = document.getElementById("myButton");
myButton.classList.add("active"); // Error: Object is possibly 'null'.
//solution
const myButton = document.getElementById("myButton");
if (myButton) {
myButton.classList.add("active"); // Safely adds the class.
}
//or
const myButton = document.getElementById("myButton");
myButton?.classList.add("active"); // Safely adds the class if myButton is not null.
```
### 3.3.4. Generics in Utility Functions
Creating utility functions for DOM manipulation without losing type information can be tricky, leading to type assertion repetition or errors.
```typescript
//example 1:
//Utility Function Without Generics:
const getElement = (id: string) => {
return document.getElementById(id); // Returns 'HTMLElement | null'.
}
getElement("myInput").value; // Error: Property 'value' does not exist.
//solution
const getElement<T extends HTMLElement> = (id: string): T | null => {
return document.getElementById(id) as T | null;
}
const myInput = getElement<HTMLInputElement>("myInput");
console.log(myInput?.value); // Correct usage with type safety.
//example 2:
const getElementsByClassName = (className: string) => {
return document.getElementsByClassName(className); // Returns 'HTMLCollectionOf<Element>'.
}
getElementsByClassName("myClass")[0].value; // Error: Property 'value' does not exist on type 'Element'.
//solution
const getElementsByClassName = <T extends Element>(className: string): HTMLCollectionOf<T> => {
return document.getElementsByClassName(className) as HTMLCollectionOf<T>;
}
const myInputs = getElementsByClassName<HTMLInputElement>("myInputClass");
console.log(myInputs[0]?.value); // Correct usage with type safety.
```
## 3.4 How to add event handlers to elements in the DOM using TypeScript
### 3.4.1. Basic Event Listening
1. Directly Adding Event Listeners
Example: Adding a click event to a button.
```typescript
const button = document.getElementById("myButton") as HTMLButtonElement;
button.addEventListener("click", (event) => {
console.log("Button clicked!");
});
```
Type-Safe Event Handler:
```typescript
button.addEventListener("click", (event: MouseEvent) => {
console.log(event.clientX, event.clientY); // Access MouseEvent properties with type safety
});
```
2. Using `this` in Event Handlers
Logging the id of a button when it's clicked.
```typescript
button.addEventListener("click", function(this: HTMLButtonElement) {
this.classList.toggle('active');
console.log(`Button ${this.id} state: ${this.classList.contains('active') ? 'Active' : 'Inactive'}`);
});
```
2. Dealing with Different Event Types
2.1 Handling Keyboard Events
Example: Adding a keypress event to an input field.
```typescript
const input = document.getElementById("myInput") as HTMLInputElement;
input.addEventListener("keypress", (event: KeyboardEvent) => {
console.log(`Key pressed: ${event.key}`);
});
```
2.2 Managing Form Submit Events
Example: Preventing a form from submitting.
```typescript
const form = document.getElementById("myForm") as HTMLFormElement;
form.addEventListener("submit", (event: SubmitEvent) => {
const inputElement = document.getElementById('myInput') as HTMLInputElement;
if (!inputElement.value.match(/^[a-zA-Z]+$/)) {
event.preventDefault(); // Prevent form submission
console.log('Form submission prevented due to validation failure.');
alert('Please enter only letters.');
} else {
console.log('Form submitted successfully.');
}
});
```
### 3.4.3. Advanced Event Handling Strategies
**Event Delegation**
Instead of adding an event listener to each element, add a single listener to a parent element and use event delegation.
```typescript
//Example: Handling clicks on multiple buttons within a container.
const container = document.getElementById("buttonContainer") as HTMLElement;
container.addEventListener("click", (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (target.tagName === "BUTTON") {
console.log(`Button ${target.id} clicked`);
}
});
```
## 3.5 Using fetch method with TypeScript
Generic solution
```typescript
function api<T>(url: string, options?: RequestInit): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<{ data: T }>;
})
.then(data => data.data)
.catch((error: Error) => {
console.error(error); // Log the error or use an external logging service
throw error; // Rethrow for further handling
});
}
```
Example 1: Fetching user information
```typescript
interface User {
id: number;
name: string;
email: string;
}
// Fetching user data
api<User>('/api/users/1')
.then(user => {
console.log(user.name, user.email);
})
.catch(error => {
console.error("Failed to fetch user data:", error);
});
```
Example 2: Posting Data to an API
```typescript
interface Post {
userId: number;
title: string;
body: string;
}
//1. Function to post data
async function createPost(postData: Post): Promise<Post> {
return api<Post>('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
}
// Usage
const newPost: Post = {
userId: 1,
title: 'Hello TypeScript',
body: 'TypeScript with Fetch API is awesome!',
};
createPost(newPost)
.then(post => console.log("Post created:", post))
.catch(error => console.error("Failed to create post:", error));
//2. Update post
async function updatePost(postId: number, postData: Post): Promise<Post> {
return api<Post>(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
}
// Usage
const updatedPost: Post = {
userId: 1,
title: 'Updated Title',
body: 'Updated body content.',
};
updatePost(1, updatedPost)
.then(post => console.log("Post updated:", post))
.catch(error => console.error("Failed to update post:", error));
//3. Delete post
async function deletePost(postId: number): Promise<void> {
return api<void>(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'DELETE',
});
}
// Usage
deletePost(1)
.then(() => console.log("Post deleted successfully."))
.catch(error => console.error("Failed to delete post:", error));
```
## 3.6 Async await and Promises with TypeScript
Promise
```typescript
//create a promise
const myPromise: Promise<string> = new Promise((resolve, reject) => {
const isSuccess = true; // Simulated outcome
isSuccess ? resolve("Operation successful") : reject("Operation failed");
});
//handle promise using .then and .catch
myPromise
.then((result: string) => console.log(result)) // Success path
.catch((error: string) => console.error(error)); // Error path
```
Using async - await
```typescript
// Create a promise
const myPromise: Promise<string> = new Promise((resolve, reject) => {
const isSuccess = true; // Simulated outcome
isSuccess ? resolve("Operation successful") : reject("Operation failed");
});
// Function to handle the promise using async - await
const handleMyPromise = async () => {
try {
const result: string = await myPromise; // Await the resolution of the promise
console.log(result);
} catch (error) {
console.error(error);
}
};
// Execute the async function
handleMyPromise();
```
### 3.6.1 Handling Multiple Promises Concurrently
```typescript
const fetchAllData = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetch('https://api.example1.com').then(res => res.json()),
fetch('https://api.example2.com').then(res => res.json()),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
//--------- Or
const fetchJsonData = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);
}
return await response.json();
};
const fetchAllDataConcurrently = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetchJsonData('https://api.example1.com'),
fetchJsonData('https://api.example2.com'),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
// Invoke the async function
fetchAllDataConcurrently();
```
### 3.6.2 Sequential Promise Execution
```typescript
const fetchAllData = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetch('https://api.example1.com').then(res => res.json()),
fetch('https://api.example2.com').then(res => res.json()),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
//--------- or
const fetchJsonData = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);
}
return await response.json();
};
const fetchSequentialData = async (): Promise<void> => {
try {
const data1 = await fetchJsonData('https://api.example1.com');
const data2 = await fetchJsonData('https://api.example2.com');
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data sequentially:", error);
}
};
// Invoke the async function
fetchSequentialData();
```
### Other example 1: Processing Files Asynchronously
```typescript
// Assuming these are the accessible URLs to files served over HTTP
const fileURLs = [
'/file1.txt',
'/file2.txt'
];
const processFile = async (fileUrl: string): Promise<string> => {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text();
return data
} catch (error) {
console.error(`Error processing file ${fileUrl}:`, error);
throw error; // Rethrow or handle as needed
}
};
const processAllFiles = async (): Promise<string[]> => {
const promises = fileURLs.map(fileUrl => processFile(fileUrl));
return Promise.all(promises);
};
// Invoke the async function
processAllFiles()
.then(results => console.log(results))
.catch(error => console.error("Failed to process files:", error));
```
### Other example 2: Delayed Operations in Sequence
Let's say you have an array of items, and for each item, you want to perform an operation that requires waiting for a certain amount of time (a simulated delay) before proceeding to the next item. This pattern is useful for rate-limiting or when dealing with APIs that have usage constraints.
```typescript
const items = [1, 2, 3, 4, 5];
const delayedOperation = async (item: number): Promise<number> => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Processed item ${item}`);
resolve(item * 2); // Example operation
}, 1000 * item); // Delay increases with each item
});
};
const processItemsSequentially = async (): Promise<number[]> => {
const results = [];
for (const item of items) {
const result = await delayedOperation(item);
results.push(result);
}
return results;
};
// Invoke the async function
processItemsSequentially()
.then(results => console.log("Final results:", results))
.catch(error => console.error("Failed to process items:", error));
```
#### Example mimics the multi-step sign-up process using Delayed Operations in Sequence
In this scenario, we have a series of validation steps that must be completed sequentially. After each step, the user receives feedback on the progress.
We'll simulate these steps with asynchronous functions that resolve after a delay, representing time-consuming operations like server validations or database checks.
```typescript
type ValidationStepResult = {
step: number;
message: string;
isValid: boolean;
};
const simulateValidationStep = async (step: number): Promise<ValidationStepResult> => {
return new Promise((resolve) => {
setTimeout(() => {
const isValid = Math.random() > 0.2; // Simulate a validation check with an 80% chance to pass
const message = isValid ? `Step ${step} completed successfully.` : `Step ${step} failed validation.`;
console.log(message); // In a real app, you would update the UI instead
resolve({ step, message, isValid });
}, 1000 * step); // Simulate a delay for each validation step
});
};
const validateSignUpProcess = async (): Promise<void> => {
const steps = [1, 2, 3, 4]; // Representing each step of the signup/validation process
let allStepsValid = true;
for (const step of steps) {
const result = await simulateValidationStep(step);
if (!result.isValid) {
allStepsValid = false;
break; // Stop the validation process if any step fails
}
}
if (allStepsValid) {
console.log("All steps completed successfully. User successfully signed up.");
// Proceed with the next step in the signup process, e.g., creating the user account
} else {
console.log("Signup process halted due to failed validation step.");
// Provide feedback to the user and potentially allow them to correct the input
}
};
// Invoke the validation process
validateSignUpProcess().catch(error => console.error("Signup validation process encountered an error:", error));
//example console for all step success senarios
// main.js:7 Step 1 completed successfully.
// main.js:7 Step 2 completed successfully.
// main.js:7 Step 3 completed successfully.
// main.js:7 Step 4 completed successfully.
// main.js:23 All steps completed successfully. User successfully signed up.
//example console for a fail senario
// main.js:7 Step 1 completed successfully.
// main.js:7 Step 2 failed validation.
// main.js:27 Signup process halted due to failed validation step.
```
## 3.7 Payload validation with TypeScript
### 3.7.1 Validate user input
#### 1. Type Guards
Example 1:
```typescript
interface LoginForm {
username: string;
password: string;
}
// Type guard for LoginForm
function isLoginForm(data: any): data is LoginForm {
return typeof data === 'object' &&
typeof data.username === 'string' &&
typeof data.password === 'string';
}
// Sample usage
const formData = { username: 'user123', password: 'pass123' }; // Assume this comes from user input
if (isLoginForm(formData)) {
console.log("Valid login form data:", formData);
} else {
console.error("Invalid login form data");
}
```
Example 2: File Upload Validation
```typescript
interface UploadedFile {
size: number; // File size in bytes
type: string; // MIME type
}
// Type guard for UploadedFile
function isValidFile(file: any): file is UploadedFile {
const maxSize = 5 * 1024 * 1024; // 5 MB
const allowedTypes = ['image/jpeg', 'image/png', 'text/plain'];
return typeof file === 'object' &&
typeof file.size === 'number' && file.size <= maxSize &&
typeof file.type === 'string' && allowedTypes.includes(file.type);
}
// Sample usage with a mock file object
const file = { size: 300000, type: 'image/jpeg' }; // Assume this comes from an <input type="file" />
if (isValidFile(file)) {
console.log("File is valid for upload:", file);
} else {
console.error("File is invalid");
}
```
#### 2. Type Generation from JSON
Create a JSON file (userInputRules.json) defining the expected structure of data from an API call:
In `tsconfig.json`
```json
...
"resolveJsonModule": true,
...
```
`userInputRules.json`
```json
{
"username": {
"minLength": 3,
"maxLength": 20
},
"password": {
"minLength": 8,
"requireSpecialCharacter": true
}
}
```
```typescript
import * as userInputRules from './userInputRules.json';
type UserInputRules = typeof userInputRules;
function validateUserInput(input: { username: string; password: string }, rules: UserInputRules): boolean {
// Simplified example of using the rules for validation
const { username, password } = input;
if (username.length < rules.username.minLength || username.length > rules.username.maxLength) {
console.error("Username validation failed.");
return false;
}
// Example check for a special character in the password
const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
if (password.length < rules.password.minLength || (rules.password.requireSpecialCharacter && !hasSpecialCharacter)) {
console.error("Password validation failed.");
return false;
}
return true;
}
// Sample usage
const userInput = { username: "user1", password: "pass!word123" };
validateUserInput(userInput, userInputRules);
```
#### 3. library: yup
```typescript
interface UserRegistrationForm {
username: string;
password: string;
email: string;
}
//----
import * as yup from 'yup';
const registrationSchema = yup.object({
username: yup.string().required().min(3).max(20),
password: yup.string()
.min(8, 'Password must be at least 8 characters long')
.matches(/[!@#$%^&*(),.?":{}|<>]/, 'Password must contain at least one special character')
.required('Password is required');
email: yup.string().email().required(),
});
const validateUserRegistration = async (formData: any): Promise<UserRegistrationForm> => {
try {
return await registrationSchema.validate(formData);
} catch (error) {
console.error("Validation error:", error);
throw error;
}
};
```
### 3.7.2 Validating API Response Data
#### 1. Typeguards
```typescript
interface UserProfile {
id: number;
name: string;
email: string;
}
// Type guard for UserProfile
function isUserProfile(data: any): data is UserProfile {
return typeof data === 'object' &&
typeof data.id === 'number' &&
typeof data.name === 'string' &&
typeof data.email === 'string';
}
// Simulated API call
async function fetchUserProfile(userId: number): Promise<UserProfile | null> {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
if (isUserProfile(data)) {
return data;
} else {
console.error("Invalid user profile data");
return null;
}
}
```
#### 2. Type Generation from JSON
Create a JSON file (apiDataSchema.json) defining the expected structure of data from an API call:
In `tsconfig.json`
```json
...
"resolveJsonModule": true,
...
```
`apiDataSchema.json`
```json
{
"userId": "number",
"name": "string",
"email": "string"
}
```
```typescript
import {default as apiDataSchema} from './apiDataSchema.json';
type APIDataSchema = typeof apiDataSchema;
async function fetchAndValidateUserData(url: string, schema: APIDataSchema): Promise<any> {
const response = await fetch(url);
// const userData = await response.json();
//check data:
const userData = {
userId: 1,
name:"John Cena"
email: "john.cena@gmail.com"
}
// Check if all fields in userData match the schema
const isValid = Object.entries(schema).every(([field, type]) => typeof userData[field] === type);
if (!isValid) {
console.error("API data validation failed.");
return null;
}
return userData;
}
// Sample usage
const apiUrl = "https://api.example.com/user/1";
fetchAndValidateUserData(apiUrl, apiDataSchema).then(data => {
if (data) {
console.log("Validated user data:", data);
}
});
```
#### 3. Using library: yup
```typescript
import * as yup from 'yup';
interface ApiResponse {
userId: number;
username: string;
email: string;
}
const registrationSchema = yup.object({
username: yup.string().required().min(3),
password: yup.string().required().min(8),
email: yup.string().email().required(),
});
const fetchUserData = async (userId: number): Promise<ApiResponse> => {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
try {
await registrationSchema.validate(data);
} catch (error) {
throw new Error("Invalid API response format.");
}
return data as ApiResponse;
};
```