Master modern web development with JavaScript including tools, testing, TypeScript, frameworks, and deployment strategies.
Master modern web development with JavaScript including tools, testing, TypeScript, frameworks, and deployment strategies.
Master modern JavaScript development tools, build systems, and development workflows for professional web development
Content by: Ritika Rajpara
React.js Developer
Modern JavaScript development requires a sophisticated toolchain that includes package managers, bundlers, transpilers, and development servers. Understanding these tools is essential for building scalable applications.
// Package.json example
{
"name": "my-javascript-project",
"version": "1.0.0",
"description": "A modern JavaScript project",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "jest",
"lint": "eslint src/**/*.js",
"format": "prettier --write src/**/*.js"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.4.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.0",
"eslint": "^8.40.0",
"prettier": "^2.8.8",
"jest": "^29.5.0"
}
}
// npm commands
// npm install package-name
// npm install --save-dev package-name
// npm run script-name
// npm update
// npm audit
// yarn commands (alternative to npm)
// yarn add package-name
// yarn add --dev package-name
// yarn run script-name
// yarn upgrade
// yarn audit
// pnpm commands (faster alternative)
// pnpm add package-name
// pnpm add --save-dev package-name
// pnpm run script-name
// pnpm update
// Vite configuration (vite.config.js)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['axios', 'lodash']
}
}
}
}
});
// Webpack configuration (webpack.config.js)
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [],
devServer: {
contentBase: './dist',
hot: true
}
};
// Rollup configuration (rollup.config.js)
import { defineConfig } from 'rollup';
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
export default defineConfig({
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(),
babel({ babelHelpers: 'bundled' })
]
});
// Babel configuration (.babelrc)
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions"]
},
"useBuiltIns": "usage",
"corejs": 3
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
]
}
// Modern JavaScript features that get transpiled
// Arrow functions
const add = (a, b) => a + b;
// Template literals
const message = `Hello ${name}!`;
// Destructuring
const { name, age } = user;
// Spread operator
const newArray = [...oldArray, newItem];
// Optional chaining
const value = user?.profile?.settings?.theme;
// Nullish coalescing
const defaultValue = user?.name ?? 'Anonymous';
// Class properties
class User {
name = 'John';
age = 30;
greet = () => {
return `Hello ${this.name}`;
};
}
// These get transformed to ES5 compatible code
// Exercise: Set up a complete development environment
// 1. Initialize a new project
// npm init -y
// 2. Install essential dependencies
// npm install --save-dev vite @vitejs/plugin-react eslint prettier
// 3. Create project structure
/*
my-project/
โโโ src/
โ โโโ components/
โ โโโ utils/
โ โโโ styles/
โ โโโ index.js
โโโ public/
โโโ dist/
โโโ package.json
โโโ vite.config.js
โโโ .eslintrc.js
โโโ .prettierrc
โโโ README.md
*/
// 4. Configure Vite (vite.config.js)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true
},
build: {
outDir: 'dist',
sourcemap: true
}
});
// 5. Configure ESLint (.eslintrc.js)
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended'
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'prefer-const': 'error'
}
};
// 6. Configure Prettier (.prettierrc)
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
// 7. Add scripts to package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src --ext .js,.jsx",
"lint:fix": "eslint src --ext .js,.jsx --fix",
"format": "prettier --write src/**/*.{js,jsx,css,md}"
}
}
// 8. Create a simple React component
// src/components/App.js
import React from 'react';
function App() {
return (
<div className="app">
<h1>Hello from Vite + React!</h1>
<p>This is a modern development setup.</p>
</div>
);
}
export default App;
// 9. Create entry point
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 10. Run the development server
// npm run dev
Test your understanding of this topic:
Learn testing strategies, frameworks, and best practices for ensuring JavaScript code quality and reliability
Content by: Parth Patel
Node.js Developer
Testing is crucial for building reliable JavaScript applications. Modern testing frameworks provide powerful tools for unit testing, integration testing, and end-to-end testing to ensure code quality and prevent regressions.
// Jest configuration (jest.config.js)
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapping: {
'\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js'
},
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!src/index.js',
'!src/reportWebVitals.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
// Basic Jest test
// math.test.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
describe('Math functions', () => {
test('adds two numbers correctly', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('subtracts two numbers correctly', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(1, 1)).toBe(0);
expect(subtract(0, 5)).toBe(-5);
});
});
// Async testing
// api.test.js
async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
describe('API functions', () => {
test('fetches user successfully', async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty('id');
expect(user).toHaveProperty('name');
expect(user.id).toBe(1);
});
test('handles API errors', async () => {
await expect(fetchUser(999)).rejects.toThrow();
});
});
// Mocking
// userService.test.js
import { getUser, createUser } from './userService';
// Mock the fetch function
global.fetch = jest.fn();
describe('User Service', () => {
beforeEach(() => {
fetch.mockClear();
});
test('gets user successfully', async () => {
const mockUser = { id: 1, name: 'John Doe' };
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockUser
});
const user = await getUser(1);
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
test('creates user successfully', async () => {
const newUser = { name: 'Jane Doe', email: 'jane@example.com' };
const createdUser = { id: 2, ...newUser };
fetch.mockResolvedValueOnce({
ok: true,
json: async () => createdUser
});
const result = await createUser(newUser);
expect(result).toEqual(createdUser);
});
});
// React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';
// Component testing
describe('App Component', () => {
test('renders welcome message', () => {
render(<App />);
expect(screen.getByText(/welcome/i)).toBeInTheDocument();
});
test('handles button click', () => {
render(<App />);
const button = screen.getByRole('button', { name: /click me/i });
fireEvent.click(button);
expect(screen.getByText(/clicked/i)).toBeInTheDocument();
});
test('handles form submission', async () => {
const user = userEvent.setup();
render(<App />);
const input = screen.getByLabelText(/name/i);
const submitButton = screen.getByRole('button', { name: /submit/i });
await user.type(input, 'John Doe');
await user.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/hello john doe/i)).toBeInTheDocument();
});
});
});
// Custom hook testing
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter Hook', () => {
test('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
test('initializes with custom value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
test('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('decrements counter', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
});
// Integration testing
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const renderWithRouter = (component) => {
return render(
<BrowserRouter>
{component}
</BrowserRouter>
);
};
describe('App Integration', () => {
test('navigates between pages', () => {
renderWithRouter(<App />);
const homeLink = screen.getByText(/home/i);
const aboutLink = screen.getByText(/about/i);
fireEvent.click(aboutLink);
expect(screen.getByText(/about page/i)).toBeInTheDocument();
fireEvent.click(homeLink);
expect(screen.getByText(/home page/i)).toBeInTheDocument();
});
});
// Cypress E2E Testing
// cypress/e2e/todo.cy.js
describe('Todo App', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('should add a new todo', () => {
const todoText = 'Learn Cypress testing';
cy.get('[data-testid="todo-input"]')
.type(todoText);
cy.get('[data-testid="add-todo-button"]')
.click();
cy.get('[data-testid="todo-list"]')
.should('contain', todoText);
});
it('should mark todo as complete', () => {
// Add a todo first
cy.get('[data-testid="todo-input"]')
.type('Test todo');
cy.get('[data-testid="add-todo-button"]')
.click();
// Mark as complete
cy.get('[data-testid="todo-checkbox"]')
.first()
.check();
cy.get('[data-testid="todo-item"]')
.first()
.should('have.class', 'completed');
});
it('should delete a todo', () => {
// Add a todo first
cy.get('[data-testid="todo-input"]')
.type('Delete me');
cy.get('[data-testid="add-todo-button"]')
.click();
// Delete the todo
cy.get('[data-testid="delete-todo-button"]')
.first()
.click();
cy.get('[data-testid="todo-list"]')
.should('not.contain', 'Delete me');
});
});
// Playwright E2E Testing
// tests/todo.spec.js
import { test, expect } from '@playwright/test';
test.describe('Todo Application', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('should add and complete todos', async ({ page }) => {
// Add a new todo
await page.fill('[data-testid="todo-input"]', 'Learn Playwright');
await page.click('[data-testid="add-todo-button"]');
// Verify todo was added
await expect(page.locator('[data-testid="todo-list"]'))
.toContainText('Learn Playwright');
// Mark as complete
await page.check('[data-testid="todo-checkbox"]');
await expect(page.locator('[data-testid="todo-item"]').first())
.toHaveClass(/completed/);
});
test('should handle multiple todos', async ({ page }) => {
const todos = ['First todo', 'Second todo', 'Third todo'];
for (const todo of todos) {
await page.fill('[data-testid="todo-input"]', todo);
await page.click('[data-testid="add-todo-button"]');
}
await expect(page.locator('[data-testid="todo-item"]'))
.toHaveCount(3);
});
});
// Test configuration
// playwright.config.js
module.exports = {
testDir: './tests',
timeout: 30000,
expect: {
timeout: 5000
},
use: {
headless: false,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
video: 'on-first-retry',
screenshot: 'only-on-failure'
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' }
},
{
name: 'firefox',
use: { browserName: 'firefox' }
},
{
name: 'webkit',
use: { browserName: 'webkit' }
}
]
};
// Exercise: Build a complete testing suite
// 1. Create a simple calculator module
// src/calculator.js
export class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
power(base, exponent) {
return Math.pow(base, exponent);
}
sqrt(number) {
if (number < 0) {
throw new Error('Cannot calculate square root of negative number');
}
return Math.sqrt(number);
}
}
// 2. Create unit tests
// tests/calculator.test.js
import { Calculator } from '../src/calculator.js';
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('Basic Operations', () => {
test('adds two positive numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(calculator.add(-1, -2)).toBe(-3);
});
test('subtracts two numbers', () => {
expect(calculator.subtract(5, 3)).toBe(2);
});
test('multiplies two numbers', () => {
expect(calculator.multiply(4, 5)).toBe(20);
});
test('divides two numbers', () => {
expect(calculator.divide(10, 2)).toBe(5);
});
});
describe('Edge Cases', () => {
test('throws error on division by zero', () => {
expect(() => calculator.divide(5, 0)).toThrow('Division by zero');
});
test('throws error on negative square root', () => {
expect(() => calculator.sqrt(-4)).toThrow('Cannot calculate square root of negative number');
});
test('handles zero operations', () => {
expect(calculator.add(0, 0)).toBe(0);
expect(calculator.multiply(5, 0)).toBe(0);
});
});
describe('Advanced Operations', () => {
test('calculates power correctly', () => {
expect(calculator.power(2, 3)).toBe(8);
expect(calculator.power(5, 0)).toBe(1);
});
test('calculates square root correctly', () => {
expect(calculator.sqrt(16)).toBe(4);
expect(calculator.sqrt(0)).toBe(0);
});
});
});
// 3. Create integration tests
// tests/calculator.integration.test.js
import { Calculator } from '../src/calculator.js';
describe('Calculator Integration', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
test('performs complex calculation chain', () => {
const result = calculator
.add(10, 5) // 15
.multiply(2) // 30
.subtract(5) // 25
.divide(5); // 5
expect(result).toBe(5);
});
test('handles multiple operations', () => {
const a = calculator.add(3, 4); // 7
const b = calculator.multiply(2, 3); // 6
const c = calculator.subtract(a, b); // 1
expect(c).toBe(1);
});
});
// 4. Create performance tests
// tests/calculator.performance.test.js
import { Calculator } from '../src/calculator.js';
describe('Calculator Performance', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
test('performs 1000 operations quickly', () => {
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
calculator.add(i, i + 1);
}
const endTime = performance.now();
const duration = endTime - startTime;
expect(duration).toBeLessThan(100); // Should complete in less than 100ms
});
});
// 5. Run tests with coverage
// npm test -- --coverage
// 6. Generate test report
// npm test -- --coverage --coverageReporters=html
Test your understanding of this topic:
Master TypeScript integration, type systems, and how to leverage TypeScript for building robust JavaScript applications
Content by: Bansi Patel
Node.js Developer
TypeScript is a superset of JavaScript that adds static typing, interfaces, and advanced language features. It helps catch errors early, provides better tooling support, and makes JavaScript code more maintainable.
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"exclude": ["node_modules"]
}
// Basic TypeScript types
let name: string = 'John Doe';
let age: number = 30;
let isActive: boolean = true;
let hobbies: string[] = ['reading', 'coding'];
let user: { name: string; age: number } = { name: 'John', age: 30 };
// Union types
let status: 'loading' | 'success' | 'error' = 'loading';
let id: string | number = 'abc123';
// Type aliases
type User = {
id: number;
name: string;
email: string;
isActive: boolean;
};
type UserStatus = 'active' | 'inactive' | 'pending';
// Interfaces
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}
interface ProductWithDiscount extends Product {
discount: number;
discountedPrice: number;
}
// Function types
type MathFunction = (a: number, b: number) => number;
type CallbackFunction = (data: any) => void;
const add: MathFunction = (a, b) => a + b;
const multiply: MathFunction = (a, b) => a * b;
// Generic types
function identity<T>(arg: T): T {
return arg;
}
function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
// Usage
const stringArray = createArray(3, 'hello');
const numberArray = createArray(5, 0);
// React component with TypeScript
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
interface UserCardProps {
user: User;
onEdit: (user: User) => void;
onDelete: (id: number) => void;
isAdmin?: boolean;
}
const UserCard: React.FC<UserCardProps> = ({
user,
onEdit,
onDelete,
isAdmin = false
}) => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [editedUser, setEditedUser] = useState<User>(user);
const handleSave = () => {
onEdit(editedUser);
setIsEditing(false);
};
const handleCancel = () => {
setEditedUser(user);
setIsEditing(false);
};
return (
<div className="user-card">
{isEditing ? (
<div className="edit-form">
<input
type="text"
value={editedUser.name}
onChange={(e) => setEditedUser({
...editedUser,
name: e.target.value
})}
/>
<input
type="email"
value={editedUser.email}
onChange={(e) => setEditedUser({
...editedUser,
email: e.target.value
})}
/>
<button onClick={handleSave}>Save</button>
<button onClick={handleCancel}>Cancel</button>
</div>
) : (
<div className="user-info">
<h3>{user.name}</h3>
<p>{user.email}</p>
{user.avatar && (
<img src={user.avatar} alt={user.name} />
)}
<div className="actions">
<button onClick={() => setIsEditing(true)}>Edit</button>
{isAdmin && (
<button onClick={() => onDelete(user.id)}>Delete</button>
)}
</div>
</div>
)}
</div>
);
};
// Custom hooks with TypeScript
interface UseCounterReturn {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
setCount: (value: number) => void;
}
const useCounter = (initialValue: number = 0): UseCounterReturn => {
const [count, setCount] = useState<number>(initialValue);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(initialValue);
return {
count,
increment,
decrement,
reset,
setCount
};
};
// API service with TypeScript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface UserService {
getUsers(): Promise<ApiResponse<User[]>>;
getUser(id: number): Promise<ApiResponse<User>>;
createUser(user: Omit<User, 'id'>): Promise<ApiResponse<User>>;
updateUser(id: number, user: Partial<User>): Promise<ApiResponse<User>>;
deleteUser(id: number): Promise<ApiResponse<void>>;
}
class UserApiService implements UserService {
private baseUrl: string = 'https://api.example.com/users';
async getUsers(): Promise<ApiResponse<User[]>> {
const response = await fetch(this.baseUrl);
const data = await response.json();
return {
data,
status: response.status,
message: 'Users retrieved successfully'
};
}
async getUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`${this.baseUrl}/${id}`);
const data = await response.json();
return {
data,
status: response.status,
message: 'User retrieved successfully'
};
}
async createUser(user: Omit<User, 'id'>): Promise<ApiResponse<User>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
});
const data = await response.json();
return {
data,
status: response.status,
message: 'User created successfully'
};
}
async updateUser(id: number, user: Partial<User>): Promise<ApiResponse<User>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
});
const data = await response.json();
return {
data,
status: response.status,
message: 'User updated successfully'
};
}
async deleteUser(id: number): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE'
});
return {
data: undefined,
status: response.status,
message: 'User deleted successfully'
};
}
}
// Exercise: Build a TypeScript Todo Application
// 1. Define types and interfaces
interface Todo {
id: number;
title: string;
description?: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
createdAt: Date;
updatedAt: Date;
}
interface TodoFilters {
completed?: boolean;
priority?: Todo['priority'];
search?: string;
}
interface TodoStats {
total: number;
completed: number;
pending: number;
byPriority: Record<Todo['priority'], number>;
}
// 2. Create Todo service
class TodoService {
private todos: Todo[] = [];
private nextId: number = 1;
addTodo(todoData: Omit<Todo, 'id' | 'completed' | 'createdAt' | 'updatedAt'>): Todo {
const todo: Todo = {
...todoData,
id: this.nextId++,
completed: false,
createdAt: new Date(),
updatedAt: new Date()
};
this.todos.push(todo);
return todo;
}
getTodos(filters?: TodoFilters): Todo[] {
let filteredTodos = [...this.todos];
if (filters?.completed !== undefined) {
filteredTodos = filteredTodos.filter(todo => todo.completed === filters.completed);
}
if (filters?.priority) {
filteredTodos = filteredTodos.filter(todo => todo.priority === filters.priority);
}
if (filters?.search) {
const searchLower = filters.search.toLowerCase();
filteredTodos = filteredTodos.filter(todo =>
todo.title.toLowerCase().includes(searchLower) ||
todo.description?.toLowerCase().includes(searchLower)
);
}
return filteredTodos;
}
getTodo(id: number): Todo | undefined {
return this.todos.find(todo => todo.id === id);
}
updateTodo(id: number, updates: Partial<Omit<Todo, 'id' | 'createdAt'>>): Todo | undefined {
const todoIndex = this.todos.findIndex(todo => todo.id === id);
if (todoIndex === -1) return undefined;
this.todos[todoIndex] = {
...this.todos[todoIndex],
...updates,
updatedAt: new Date()
};
return this.todos[todoIndex];
}
deleteTodo(id: number): boolean {
const initialLength = this.todos.length;
this.todos = this.todos.filter(todo => todo.id !== id);
return this.todos.length < initialLength;
}
toggleTodo(id: number): Todo | undefined {
const todo = this.getTodo(id);
if (!todo) return undefined;
return this.updateTodo(id, { completed: !todo.completed });
}
getStats(): TodoStats {
const total = this.todos.length;
const completed = this.todos.filter(todo => todo.completed).length;
const pending = total - completed;
const byPriority = {
low: this.todos.filter(todo => todo.priority === 'low').length,
medium: this.todos.filter(todo => todo.priority === 'medium').length,
high: this.todos.filter(todo => todo.priority === 'high').length
};
return { total, completed, pending, byPriority };
}
}
// 3. Create React components with TypeScript
import React, { useState, useEffect } from 'react';
interface TodoAppProps {
service: TodoService;
}
const TodoApp: React.FC<TodoAppProps> = ({ service }) => {
const [todos, setTodos] = useState<Todo[]>([]);
const [filters, setFilters] = useState<TodoFilters>({});
const [stats, setStats] = useState<TodoStats>({
total: 0,
completed: 0,
pending: 0,
byPriority: { low: 0, medium: 0, high: 0 }
});
useEffect(() => {
updateTodos();
updateStats();
}, [filters]);
const updateTodos = () => {
setTodos(service.getTodos(filters));
};
const updateStats = () => {
setStats(service.getStats());
};
const handleAddTodo = (todoData: Omit<Todo, 'id' | 'completed' | 'createdAt' | 'updatedAt'>) => {
service.addTodo(todoData);
updateTodos();
updateStats();
};
const handleToggleTodo = (id: number) => {
service.toggleTodo(id);
updateTodos();
updateStats();
};
const handleDeleteTodo = (id: number) => {
service.deleteTodo(id);
updateTodos();
updateStats();
};
return (
<div className="todo-app">
<TodoForm onAdd={handleAddTodo} />
<TodoFilters filters={filters} onFiltersChange={setFilters} />
<TodoList
todos={todos}
onToggle={handleToggleTodo}
onDelete={handleDeleteTodo}
/>
<TodoStats stats={stats} />
</div>
);
};
// 4. Test the application
const todoService = new TodoService();
// Add some todos
todoService.addTodo({
title: 'Learn TypeScript',
description: 'Master TypeScript fundamentals',
priority: 'high'
});
todoService.addTodo({
title: 'Build React app',
description: 'Create a todo application',
priority: 'medium'
});
todoService.addTodo({
title: 'Write tests',
description: 'Add unit and integration tests',
priority: 'low'
});
// Get todos with filters
const highPriorityTodos = todoService.getTodos({ priority: 'high' });
const completedTodos = todoService.getTodos({ completed: true });
// Get statistics
const stats = todoService.getStats();
console.log('Todo Statistics:', stats);
Test your understanding of this topic:
Explore modern JavaScript frameworks, libraries, and architectural patterns for building scalable web applications
Content by: Raj Koradiya
Node.js Developer
Modern JavaScript frameworks provide powerful abstractions for building complex web applications. Understanding different frameworks and their use cases helps developers choose the right tools for their projects.
Test your understanding of this topic:
Master deployment strategies, performance optimization, and production considerations for JavaScript applications
Content by: Dipen Dalvadi
.Net Developer
Deploying JavaScript applications to production requires careful consideration of performance, security, monitoring, and maintenance. Understanding deployment strategies ensures applications run smoothly in production environments.
Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Back to Course Overview