coding exercises in React, Angular etc - realestateexplorer/relatedToCh GitHub Wiki
create coding exercises in React
Of course! Here is a set of React coding exercises, broken down by difficulty level. Each exercise focuses on specific core concepts.
-
Setup: You can use an online code editor like CodeSandbox or StackBlitz for a zero-config start. Alternatively, set up a local project using Vite (
npm create vite@latest
) for a fast, modern development environment. - Process: Try to solve each exercise on your own first. If you get stuck, look at the "Concepts to Use" for a hint. The "Bonus Challenge" is there to push you further.
-
Focus on Why: Don't just copy-paste code. Understand why a particular hook (
useState
,useEffect
) or pattern is used.
These exercises cover the absolute basics: components, props, state, and event handling.
Goal: Understand components and props.
-
Concepts to Use: Functional Components, JSX, Props.
-
Task:
- Create a component named
Greeting
. - This component should accept a
name
prop. - It should render a
<h1>
tag with the text "Hello, [name]!". - In your main
App
component, use theGreeting
component and pass it your name.
- Create a component named
-
Example Usage in
App.js
:function App() { return <Greeting name="Alice" />; }
-
Bonus Challenge: Create another component called
UserDetails
that takes an object prop like{ name: 'Bob', age: 30 }
and displays the details in a paragraph.
Goal: Understand state and event handling.
-
Concepts to Use:
useState
hook, Event Handling (onClick
). -
Task:
- Create a component named
Counter
. - It should display a number (the count) and a button.
- The number should start at 0.
- When the user clicks the button, the count should increase by 1.
- Create a component named
-
Bonus Challenge: Add two more buttons: one to decrease the count (but not below 0) and another to reset it to 0.
Goal: Understand conditional rendering.
-
Concepts to Use:
useState
, Conditional Rendering (Ternary operator or&&
). -
Task:
- Create a component named
ToggleVisibility
. - It should have a button that says "Show/Hide Text".
- Below the button, there is a paragraph of text.
- When the component first renders, the text should be hidden.
- When the button is clicked, the text should appear. If clicked again, it should disappear.
- Create a component named
-
Bonus Challenge: Instead of a single paragraph, make it toggle an "Accordion" item with a title and content.
Goal: Understand rendering lists from an array.
-
Concepts to Use:
useState
with an array,.map()
method,key
prop. -
Task:
- Create a component named
TodoList
. - In your component's state, store an array of to-do items (e.g.,
['Learn React', 'Build a project', 'Go for a walk']
). - Render this array as an unordered list (
<ul>
), where each to-do item is a list item (<li>
). - Remember to give each list item a unique
key
.
- Create a component named
-
Bonus Challenge: Add an input field and a button to allow users to add new items to the to-do list.
These exercises introduce forms, side effects (data fetching), and state management patterns.
Goal: Understand how to handle user input with controlled components.
-
Concepts to Use:
useState
for form fields, HandlingonChange
events. -
Task:
- Create a
LoginForm
component. - It should contain two input fields: one for
email
and one forpassword
. - The state of these inputs should be "controlled" by your React component's state.
- Add a "Submit" button.
- When the form is submitted,
console.log
an object containing the current email and password. Prevent the default form submission behavior.
- Create a
-
Bonus Challenge: Add simple validation. If the password is less than 8 characters long when the user tries to submit, display an error message below the input field.
Goal: Understand how to perform side effects, like fetching data.
-
Concepts to Use:
useEffect
hook,useState
for data and loading states,fetch
API. -
Task:
- Use a free public API, like the JSONPlaceholder Users API.
- Create a
UserList
component. - When the component mounts, use
useEffect
to fetch the list of users from the API. - While the data is being fetched, display a "Loading..." message.
- Once the data is fetched, display the list of users' names in an unordered list.
-
Bonus Challenge: Add an error state. If the API call fails (you can test this by using a broken URL), display a "Failed to fetch users." message.
Goal: Understand global state management with the Context API.
-
Concepts to Use:
createContext
,useContext
, Provider components. -
Task:
- Create a
ThemeContext
that will provide a theme value ('light'
or'dark'
) and a function to toggle it. - Wrap your main
App
component in aThemeProvider
. - Create a
ThemeToggleButton
component that usesuseContext
to access the toggle function and change the theme. - Create a
HomePage
component that usesuseContext
to access the current theme value. It should change its background color and text color based on the theme.
- Create a
-
Bonus Challenge: Use
localStorage
to save the user's theme preference so that it persists even after they refresh the page.
These exercises focus on more complex state, performance, and application structure.
Goal: Understand how to work with derived state and optimize performance.
-
Concepts to Use:
useState
,useMemo
. -
Task:
- Start with the
UserList
component from Exercise 6. - Add a search input field above the list of users.
- As the user types into the search field, the list of users should filter in real-time to only show users whose names include the search text (case-insensitive).
- The filtering logic should not cause the component to re-fetch the data.
- Start with the
-
Bonus Challenge: Wrap the filtering logic in a
useMemo
hook. This ensures that the list is only re-filtered when the original user list or the search term changes, not on other re-renders. This is a key performance optimization.
Goal: Understand complex state logic with useReducer
.
-
Concepts to Use:
useReducer
hook. -
Task:
- Refactor the
TodoList
from Exercise 4 (with the "add" functionality). - Instead of
useState
, manage the list's state usinguseReducer
. - Your reducer function should handle actions like
ADD_TODO
,DELETE_TODO
, andTOGGLE_TODO_COMPLETED
. - Implement buttons for deleting a to-do and for marking a to-do as completed (e.g., by adding a line-through style).
- Refactor the
-
Bonus Challenge: Add an "edit" functionality. When an "Edit" button is clicked on a to-do, it should become an input field, allowing the user to change its text.
Goal: Understand how to create a multi-page application.
-
Concepts to Use: A routing library like React Router.
- You will need to install it:
npm install react-router-dom
- You will need to install it:
-
Task:
- Create a small application with three "pages": a Home page, an About page, and a Users page.
- Set up routes for
/
,/about
, and/users
. - Create a navigation bar with links that allow the user to switch between these pages without a full page reload.
- The
/users
page should render theUserList
component you built in Exercise 6.
-
Bonus Challenge: Create a dynamic route
/users/:id
. When a user clicks on a name in theUserList
, it should navigate to their specific detail page, fetch only that user's data fromhttps://jsonplaceholder.typicode.com/users/:id
, and display all their details (name, email, phone, etc.).
create coding exercises in React with solutions
Of course! Here is a set of React coding exercises with solutions, structured by difficulty. Each solution includes the code and an explanation of the core concepts.
You can use an online code editor like CodeSandbox or StackBlitz to work on these without any local setup. Just paste the code into the App.js
file.
Goal: Understand useState
for managing state and onClick
for handling events.
- Task: Create a component with a number display and two buttons. One button increments the count, and the other decrements it.
Solution: Counter.js
import React, { useState } from 'react';
function Counter() {
// 1. Initialize state with useState
// 'count' is the state variable (its current value is 0)
// 'setCount' is the function to update it
const [count, setCount] = useState(0);
// 2. Create functions to handle clicks
const handleIncrement = () => {
// We use a function inside setCount to get the most recent state
setCount(prevCount => prevCount + 1);
};
const handleDecrement = () => {
setCount(prevCount => prevCount - 1);
};
return (
<div>
<h1>Counter</h1>
{/* 3. Display the current state */}
<p>Current Count: {count}</p>
{/* 4. Attach event handlers to buttons */}
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
}
export default Counter;
Explanation:
-
useState(0)
: This hook declares a state variable namedcount
. Its initial value is0
. It returns an array with two elements: the current state value (count
) and a function to update it (setCount
). -
Event Handlers: The
handleIncrement
andhandleDecrement
functions callsetCount
to change the value ofcount
. Using a function(prevCount => ...)
is a safe way to update state that depends on its previous value. -
Rendering: The current value of
count
is displayed inside the<p>
tag using curly braces{}
. -
onClick
: This attribute attaches our functions to the button's click event. When a button is clicked, the corresponding function runs, updates the state, and React re-renders the component to show the new count.
Goal: Understand conditional rendering to show or hide elements.
- Task: Create a component with a button and a paragraph of text. Clicking the button should toggle the visibility of the text.
Solution: ToggleVisibility.js
import React, { useState } from 'react';
function ToggleVisibility() {
// 1. State to track visibility, initialized to true
const [isVisible, setIsVisible] = useState(true);
const handleToggle = () => {
// 2. Update state to the opposite of its current value
setIsVisible(prevIsVisible => !prevIsVisible);
};
return (
<div>
<h1>Toggle Visibility</h1>
<button onClick={handleToggle}>
{isVisible ? 'Hide' : 'Show'} Text
</button>
{/* 3. Conditional rendering using the && operator */}
{isVisible && <p>This is the text you can toggle!</p>}
</div>
);
}
export default ToggleVisibility;
Explanation:
-
useState(true)
: We use a boolean stateisVisible
to control whether the text is shown. It starts astrue
. -
setIsVisible(!prevIsVisible)
: ThehandleToggle
function flips the boolean value. IfisVisible
istrue
, it becomesfalse
, and vice-versa. -
Conditional Rendering: The line
{isVisible && <p>...</p>}
is a common React pattern. In JavaScript,true && expression
always evaluates toexpression
, andfalse && expression
always evaluates tofalse
. React doesn't renderfalse
, so the paragraph only appears on the screen whenisVisible
istrue
. We also use a ternary operator on the button's text to make it dynamic.
Goal: Understand how to manage form inputs using state (controlled components).
- Task: Create a login form with an email input, a password input, and a submit button. When submitted, log the form data to the console.
Solution: LoginForm.js
import React, { useState } from 'react';
function LoginForm() {
// 1. Create state for each form input
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event) => {
// 2. Prevent the default form submission behavior (page reload)
event.preventDefault();
// 3. Log the current state values
console.log('Form Submitted:', { email, password });
alert(`Logged in with Email: ${email}`);
// Optional: Clear the form after submission
setEmail('');
setPassword('');
};
return (
<form onSubmit={handleSubmit}>
<h1>Login Form</h1>
<div>
<label>Email: </label>
<input
type="email"
// 4. Connect the input's value to state
value={email}
// 5. Update state whenever the input changes
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label>Password: </label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">Login</button>
</form>
);
}
export default LoginForm;
Explanation:
-
State for Inputs: Each input field has its own piece of state (
email
,password
). This is the "single source of truth." -
event.preventDefault()
: In thehandleSubmit
function, this is crucial. It stops the browser from its default action of reloading the page when a form is submitted. -
onSubmit
: This event handler is placed on the<form>
element, not the button. It triggers thehandleSubmit
function. -
value={email}
: This "controls" the input. The input's displayed value is always what's in the React state. -
onChange
: This function runs every time the user types in the input.e.target.value
contains the current text in the input box, which we use to update our state withsetEmail
. This completes the loop: user types ->onChange
fires -> state updates -> input value changes.
Goal: Understand the useEffect
hook for performing side effects like API calls, and manage loading/error states.
- Task: Fetch a list of users from the JSONPlaceholder API when the component mounts. Display a "Loading..." message while fetching, and the list of user names when done.
Solution: UserList.js
import React, { useState, useEffect } from 'react';
function UserList() {
// 1. State for users, loading status, and errors
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// 2. useEffect to fetch data when the component mounts
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setUsers(data); // 3. Set data in state
} catch (error) {
setError(error); // 4. Set error in state
} finally {
setLoading(false); // 5. Set loading to false in any case
}
};
fetchUsers();
}, []); // 6. Empty dependency array means this effect runs only once
// 7. Conditional rendering based on state
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<div>
<h1>User List</h1>
<ul>
{/* 8. Map over the users array to render a list */}
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserList;
Explanation:
-
Multiple States: We track three things: the
users
data (an array), theloading
status (a boolean), and any potentialerror
. -
useEffect
: This hook runs code after the component has rendered. It's perfect for side effects like data fetching. -
Async Function: We use an
async/await
function insideuseEffect
for cleaner asynchronous code. -
Error Handling: The
try...catch
block is essential for handling network errors. If an error occurs, we store it in theerror
state. -
finally
Block: This code runs whether thetry
block succeeded or failed. It's the perfect place to setloading
tofalse
. -
Dependency Array
[]
: An empty array tells React to run this effect only once, after the initial render. This prevents an infinite loop of fetching. -
Conditional Rendering: Before rendering the list, we check the
loading
anderror
states to display the appropriate message to the user. -
.map()
andkey
: We render theusers
array using.map()
. Thekey={user.id}
prop is crucial for React to efficiently update the list.
Goal: Understand derived state and performance optimization with the useMemo
hook.
-
Task: Build on the
UserList
component. Add a search input field. As the user types, the list of users should filter to show only those whose names include the search term.
Solution: UserSearch.js
import React, { useState, useEffect, useMemo } from 'react';
function UserSearch() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
// 1. Add state for the search term
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
setUsers(data);
} catch (error) {
console.error("Failed to fetch users", error);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
// 2. Use useMemo to calculate the filtered list
// This calculation only re-runs when 'users' or 'searchTerm' changes
const filteredUsers = useMemo(() => {
if (!searchTerm) {
return users; // Return all users if search is empty
}
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]); // 3. Dependency array for useMemo
if (loading) {
return <p>Loading...</p>;
}
return (
<div>
<h1>User Search</h1>
{/* 4. Controlled input for the search term */}
<input
type="text"
placeholder="Search by name..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<ul>
{/* 5. Render the MEMOIZED filtered list */}
{filteredUsers.length > 0 ? (
filteredUsers.map(user => <li key={user.id}>{user.name}</li>)
) : (
<p>No users found.</p>
)}
</ul>
</div>
);
}
export default UserSearch;
Explanation:
-
searchTerm
State: We add a new piece of state to hold the value of the search input. -
useMemo
: This is the key to this solution.useMemo
is a hook that memoizes a calculated value. This means it will only re-run the expensive filtering calculation if one of its dependencies changes. -
useMemo
Dependencies: The dependency array[users, searchTerm]
tellsuseMemo
to re-calculatefilteredUsers
only when the originalusers
array changes (e.g., after the initial fetch) or when thesearchTerm
changes (as the user types). If the component re-renders for any other reason, it will use the previously cached result, saving performance. -
Controlled Input: The search input is a standard controlled component, updating the
searchTerm
stateonChange
. -
Render Filtered List: We map over
filteredUsers
instead of the originalusers
array. This ensures only the matching users are displayed.
create coding exercises in Angular with solutions
Of course! Here is a set of coding exercises for Angular, complete with solutions and explanations. They are structured by difficulty to help learners progress from fundamental concepts to more advanced patterns.
-
Setup: The easiest way to work on these is to use an online IDE like StackBlitz. It provides a full Angular environment in your browser. Alternatively, you can use the Angular CLI to create a local project (
ng new my-angular-app
). - Process: For each exercise, try to build the solution yourself first. The goal is to understand the "why" behind each concept. If you get stuck, review the solution and the explanation.
These exercises focus on components, data binding, and basic directives.
Goal: Understand component properties and event binding.
- Task: Create a component that displays a count. It should have two buttons: one to increment the count and one to decrement it.
Solution: counter.component.ts
and counter.component.html
counter.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
})
export class CounterComponent {
// 1. Define a property to hold the state
count = 0;
// 2. Define methods to change the state
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
counter.component.html
<h2>Counter</h2>
<!-- 3. Display the count property using interpolation -->
<p>Current Count: {{ count }}</p>
<!-- 4. Bind the click event to the component's methods -->
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
Explanation:
-
Component Property: In the
.ts
file,count = 0;
declares a class property. This property holds the state for our component. -
Methods: The
increment()
anddecrement()
methods contain the logic to modify thecount
property. -
String Interpolation
{{ }}
: In the HTML,{{ count }}
is one-way data binding. It displays the current value of thecount
property from the component class. -
Event Binding
( )
:(click)="increment()"
is event binding. It listens for theclick
event on the button and calls theincrement()
method in the component class whenever it occurs. Angular's change detection then automatically updates the view.
Goal: Understand structural directives (*ngFor
, *ngIf
) and two-way data binding ([(ngModel)]
).
- Task: Create a component to manage a to-do list. It should display a list of tasks, and have an input field and a button to add new tasks to the list.
Setup: To use [(ngModel)]
, you must import FormsModule
in your module.
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // <-- Import FormsModule
import { AppComponent } from './app.component';
// ... other components
@NgModule({
declarations: [AppComponent /*, ...*/],
imports: [
BrowserModule,
FormsModule // <-- Add FormsModule to imports
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Solution: todo-list.component.ts
and todo-list.component.html
todo-list.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
})
export class TodoListComponent {
// 1. Property to hold the list of tasks
tasks = ['Learn Angular', 'Build a project', 'Deploy the app'];
// 2. Property to bind to the input field
newTask = '';
addTask() {
// 3. Add the new task if it's not empty
if (this.newTask.trim()) {
this.tasks.push(this.newTask);
this.newTask = ''; // Clear the input field
}
}
removeTask(index: number) {
this.tasks.splice(index, 1);
}
}
todo-list.component.html
<h2>To-Do List</h2>
<div>
<!-- 4. Two-way data binding for the input -->
<input type="text" [(ngModel)]="newTask" placeholder="Add a new task">
<button (click)="addTask()">Add Task</button>
</div>
<!-- 5. Use *ngIf to show a message if the list is empty -->
<p *ngIf="tasks.length === 0">Your to-do list is empty!</p>
<!-- 6. Use *ngFor to loop through and display the tasks -->
<ul>
<li *ngFor="let task of tasks; let i = index">
{{ task }}
<button (click)="removeTask(i)">Remove</button>
</li>
</ul>
Explanation:
-
State: The
tasks
array holds the list data, andnewTask
holds the value from the input field. -
[(ngModel)]
: This is two-way data binding. It combines property binding[value]="newTask"
and event binding(input)="newTask = $event.target.value"
into one. The input's value is always in sync with thenewTask
property. -
addTask()
: This method pushes the new task into thetasks
array and then clears the input field. -
*ngIf
: This is a structural directive. It adds or removes the<p>
element from the DOM based on the conditiontasks.length === 0
. -
*ngFor
: This directive iterates over thetasks
array. For eachtask
in the array, it creates a new<li>
element. We also get theindex
(let i = index
) to know which item to remove.
Goal: Understand how to pass data from a parent to a child (@Input
) and emit events from a child to a parent (@Output
).
-
Task: Refactor the To-Do list. Create a
TaskItemComponent
that is responsible for displaying a single task and its "Remove" button. TheTodoListComponent
will be the parent, passing data down and listening for remove events.
Solution: Parent (todo-list
) and Child (task-item
) components
todo-list.component.ts
(Parent)
import { Component } from '@angular/core';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html', // The HTML will be updated
})
export class TodoListComponent {
tasks = ['Learn components', 'Learn directives', 'Master services'];
newTask = '';
addTask() {
if (this.newTask.trim()) {
this.tasks.push(this.newTask);
this.newTask = '';
}
}
// 1. This method will be triggered by the child's event
handleRemove(taskToRemove: string) {
this.tasks = this.tasks.filter(task => task !== taskToRemove);
}
}
todo-list.component.html
(Parent)
<h2>To-Do List (with Child Components)</h2>
<div>
<input type="text" [(ngModel)]="newTask" placeholder="Add a new task">
<button (click)="addTask()">Add</button>
</div>
<ul>
<!-- 2. Loop and pass data to the child component -->
<app-task-item
*ngFor="let currentTask of tasks"
[taskName]="currentTask"
(remove)="handleRemove($event)">
</app-task-item>
</ul>
task-item.component.ts
(Child)
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-task-item',
template: `
<li>
{{ taskName }}
<!-- 4. Emit the event on click -->
<button (click)="emitRemoveEvent()">Remove</button>
</li>
`
})
export class TaskItemComponent {
// 3. @Input allows the parent to pass data IN
@Input() taskName: string = '';
// 3. @Output allows this component to send events OUT
@Output() remove = new EventEmitter<string>();
emitRemoveEvent() {
// We emit the name of the task to be removed
this.remove.emit(this.taskName);
}
}
Explanation:
-
Parent Logic: The parent (
TodoListComponent
) now has ahandleRemove
method that expects the task's name. It no longer needs to know about the index. -
Passing Data: In the parent's HTML,
[taskName]="currentTask"
uses property binding to pass the task string down to thetaskName
property of theTaskItemComponent
. -
@Input
and@Output
:- In the child,
@Input() taskName
decorates a property, marking it as a target for data from the parent. -
@Output() remove = new EventEmitter<string>()
creates a new event emitter. The child can use this to send data (in this case, astring
) back up to the parent.
- In the child,
-
Emitting an Event: When the "Remove" button in the child is clicked,
emitRemoveEvent()
is called, which in turn callsthis.remove.emit(this.taskName)
. -
Listening for the Event: Back in the parent's HTML,
(remove)="handleRemove($event)"
listens for theremove
event from the child. When it fires, it callshandleRemove()
, and the emitted value (this.taskName
) is automatically passed in as$event
.
Goal: Understand how to use Services for business logic, HttpClient
for API requests, and the async
pipe for handling Observables
.
-
Task: Create a
UserListComponent
that fetches user data from JSONPlaceholder. Use aUserService
to handle the HTTP request. Display a loading message until the data arrives.
Setup: Import HttpClientModule
in your app.module.ts
.
app.module.ts
import { HttpClientModule } from '@angular/common/http'; // <-- Import
@NgModule({
imports: [
// ... other modules
HttpClientModule // <-- Add to imports
],
// ...
})
export class AppModule { }
Solution: user.service.ts
, user-list.component.ts
, and user-list.component.html
user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
// Define an interface for the User data structure
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: 'root' // 1. Makes the service a singleton available app-wide
})
export class UserService {
private apiUrl = 'https://jsonplaceholder.typicode.com/users';
// 2. Inject HttpClient via the constructor
constructor(private http: HttpClient) { }
// 3. The method returns an Observable of User array
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
}
user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService, User } from './user.service'; // Adjust path as needed
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
})
export class UserListComponent implements OnInit {
// 4. Define a property to hold the Observable
users$!: Observable<User[]>;
// 5. Inject the UserService
constructor(private userService: UserService) { }
ngOnInit(): void {
// 6. Assign the Observable from the service to the component property
this.users$ = this.userService.getUsers();
}
}
user-list.component.html
<h2>User List from API</h2>
<!-- 7. Use the async pipe to subscribe and get data -->
<ul *ngIf="users$ | async as users; else loading">
<li *ngFor="let user of users">
{{ user.name }} ({{ user.email }})
</li>
</ul>
<!-- 8. Define a loading template -->
<ng-template #loading>
<p>Loading users...</p>
</ng-template>
Explanation:
-
@Injectable({providedIn: 'root'})
: This decorator marks the class as a service and tells Angular's dependency injection system how to provide it.providedIn: 'root'
means a single instance of this service is created for the entire application. -
Dependency Injection: The
UserService
requests an instance ofHttpClient
in its constructor. Angular automatically provides it. -
Observable:
HttpClient
methods do not return data directly. They return anObservable
, which is a stream of data that can be subscribed to. -
users$!
: In the component, we declare a propertyusers$
. The$
suffix is a common convention for properties that hold an Observable. The!
tells TypeScript that we will definitely initialize it later (inngOnInit
). -
ngOnInit
: This is a lifecycle hook that runs once after the component is initialized. It's the perfect place to make initial data requests. -
async
Pipe: This is the magic part. Theasync
pipe in the template automatically subscribes to theusers$
Observable.- While waiting for the data, it returns
null
. - Once the data arrives, it triggers change detection and provides the data.
- It automatically unsubscribes when the component is destroyed, preventing memory leaks.
- While waiting for the data, it returns
-
*ngIf ... as users
: This syntax lets us subscribe with theasync
pipe and store the resulting array in a template variable namedusers
, which we can then use in the*ngFor
. -
else loading
: The*ngIf
is also paired with anelse
clause. Ifusers$ | async
isnull
(because data hasn't arrived yet), it will render the<ng-template>
named#loading
.
create coding exercises in SQL with solutions
Of course! Here is a set of SQL coding exercises with solutions, designed for learning and practice. The exercises are structured by difficulty and use a common e-commerce database schema, which we will define first.
All exercises will be based on the following four tables. You can run this setup code in any SQL environment (like MySQL, PostgreSQL, SQL Server, or an online tool like DB Fiddle).
Schema:
-
Customers
: Stores customer information. -
Products
: Stores product information. -
Orders
: Stores order header information, linked to a customer. -
Order_Items
: A linking table that stores the specific products and quantities for each order.
SQL Setup Code:
-- Create Tables
CREATE TABLE Customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
join_date DATE
);
CREATE TABLE Products (
product_id INT PRIMARY KEY,
name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(10, 2)
);
CREATE TABLE Orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES Customers(customer_id)
);
CREATE TABLE Order_Items (
order_item_id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
FOREIGN KEY (order_id) REFERENCES Orders(order_id),
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
-- Insert Sample Data
INSERT INTO Customers (customer_id, first_name, last_name, email, join_date) VALUES
(1, 'John', 'Smith', '[email protected]', '2022-01-15'),
(2, 'Jane', 'Doe', '[email protected]', '2022-02-20'),
(3, 'Peter', 'Jones', '[email protected]', '2022-03-10'),
(4, 'Alice', 'Williams', '[email protected]', '2023-05-01');
INSERT INTO Products (product_id, name, category, price) VALUES
(1, 'Laptop', 'Electronics', 1200.00),
(2, 'Smartphone', 'Electronics', 800.00),
(3, 'Desk Chair', 'Furniture', 250.00),
(4, 'Coffee Maker', 'Appliances', 100.00),
(5, 'The Great Gatsby', 'Books', 15.00);
INSERT INTO Orders (order_id, customer_id, order_date) VALUES
(101, 1, '2023-04-10'),
(102, 1, '2023-05-22'),
(103, 2, '2023-06-05'),
(104, 3, '2023-06-08');
INSERT INTO Order_Items (order_item_id, order_id, product_id, quantity) VALUES
(1, 101, 1, 1),
(2, 101, 5, 2),
(3, 102, 3, 1),
(4, 103, 2, 1),
(5, 103, 5, 3),
(6, 104, 1, 1),
(7, 104, 2, 1);
-
Task: Write a query to select all columns for all records from the
Products
table.
Solution:
SELECT *
FROM Products;
Explanation:
-
SELECT *
means "select all columns". -
FROM Products
specifies the table you are querying.
- Task: Write a query to find the names and prices of all products in the 'Electronics' category.
Solution:
SELECT name, price
FROM Products
WHERE category = 'Electronics';
Explanation:
-
SELECT name, price
specifies that you only want these two columns. -
WHERE category = 'Electronics'
filters the rows, returning only those where thecategory
column has the value 'Electronics'.
-
Task: Write a query to get the
first_name
andjoin_date
of all customers, sorted with the newest members first.
Solution:
SELECT first_name, join_date
FROM Customers
ORDER BY join_date DESC;
Explanation:
-
ORDER BY join_date
tells the database to sort the results based on thejoin_date
column. -
DESC
stands for "descending", which sorts from the highest value (latest date) to the lowest (earliest date). The default isASC
(ascending).
-
Task: Write a query to list all
order_id
s along with thefirst_name
andlast_name
of the customer who placed the order.
Solution:
SELECT
o.order_id,
c.first_name,
c.last_name
FROM Orders AS o
JOIN Customers AS c ON o.customer_id = c.customer_id;
Explanation:
-
JOIN Customers AS c ON o.customer_id = c.customer_id
combines rows fromOrders
(aliased aso
) andCustomers
(aliased asc
). - The
ON
clause specifies the join condition: link rows where thecustomer_id
is the same in both tables. This is anINNER JOIN
by default, meaning it only includes customers who have placed orders.
- Task: Write a query to calculate the total revenue generated by each product. The result should show the product name and its total revenue, sorted from most to least revenue.
Solution:
SELECT
p.name,
SUM(oi.quantity * p.price) AS total_revenue
FROM Order_Items AS oi
JOIN Products AS p ON oi.product_id = p.product_id
GROUP BY p.name
ORDER BY total_revenue DESC;
Explanation:
-
JOIN
: We joinOrder_Items
andProducts
to get access to both quantity and price for each item sold. -
SUM(oi.quantity * p.price)
: This is an aggregate function. For each product, it calculates the revenue (quantity
*price
) for each line item and then sums them up.AS total_revenue
gives this new calculated column a name. -
GROUP BY p.name
: This is crucial. It tells theSUM
function to group all the rows by product name and perform the sum for each group separately. -
ORDER BY total_revenue DESC
: Sorts the final result to show the most profitable products first.
- Task: Write a query to find all customers who have never placed an order.
Solution:
SELECT
c.customer_id,
c.first_name,
c.last_name
FROM Customers AS c
LEFT JOIN Orders AS o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
Explanation:
-
LEFT JOIN
: This join returns all rows from the left table (Customers
), and the matched rows from the right table (Orders
). - If a customer has no matching orders, the columns from the
Orders
table (likeo.order_id
) will beNULL
for that customer's row. -
WHERE o.order_id IS NULL
: This filters the result of theLEFT JOIN
to show only those rows where there was no match—i.e., customers without orders.
- Task: Write a query to find the name and price of the most expensive product in each category.
Solution (using a Window Function):
WITH RankedProducts AS (
SELECT
name,
category,
price,
RANK() OVER (PARTITION BY category ORDER BY price DESC) as price_rank
FROM Products
)
SELECT
name,
category,
price
FROM RankedProducts
WHERE price_rank = 1;
Explanation:
-
WITH RankedProducts AS (...)
: This defines a Common Table Expression (CTE), which is like a temporary, named result set that you can query. -
RANK() OVER (...)
: This is a window function.-
PARTITION BY category
: It divides the rows into partitions (groups) based on their category. TheRANK()
function will restart for each new category. -
ORDER BY price DESC
: Within each partition, it orders the products by price from highest to lowest. -
RANK()
then assigns a rank to each product within its category based on this ordering. The most expensive product gets a rank of 1.
-
-
SELECT ... FROM RankedProducts WHERE price_rank = 1
: The final query simply selects the rows from our CTE where the rank is 1, giving us the most expensive product from each category.
- Task: Write a query to find the first and last names of all customers who have ordered a 'Laptop'.
Solution (using a Subquery):
SELECT
first_name,
last_name
FROM Customers
WHERE customer_id IN (
SELECT o.customer_id
FROM Orders AS o
JOIN Order_Items AS oi ON o.order_id = oi.order_id
JOIN Products AS p ON oi.product_id = p.product_id
WHERE p.name = 'Laptop'
);
Explanation:
- The query inside the parentheses is a subquery. It runs first.
-
Inner Query: It finds all unique
customer_id
s for orders that contain a product named 'Laptop'. -
Outer Query: It then selects the names from the
Customers
table, but only for those customers whosecustomer_id
is in the list generated by the subquery (WHERE customer_id IN (...)
).
- Task: For customer John Smith (customer_id = 1), calculate the total value of each of his orders and also show a running total of his spending over time.
Solution (using Window Functions and a CTE):
WITH CustomerOrderValue AS (
SELECT
o.customer_id,
o.order_id,
o.order_date,
SUM(oi.quantity * p.price) AS order_total
FROM Orders AS o
JOIN Order_Items AS oi ON o.order_id = oi.order_id
JOIN Products AS p ON oi.product_id = p.product_id
WHERE o.customer_id = 1
GROUP BY o.order_id, o.customer_id, o.order_date
)
SELECT
order_id,
order_date,
order_total,
SUM(order_total) OVER (ORDER BY order_date) AS running_total
FROM CustomerOrderValue
ORDER BY order_date;
Explanation:
-
CTE
CustomerOrderValue
: The first step is to calculate the total value for each individual order placed by customer 1. This is done by joining the tables, filtering forcustomer_id = 1
, and usingGROUP BY
andSUM
. - Outer Query: This query selects from the CTE.
-
SUM(order_total) OVER (ORDER BY order_date)
: This is the key window function.-
OVER (ORDER BY order_date)
tells theSUM
function to operate over a "window" of rows ordered by date. - For each row, it sums the
order_total
of all rows from the beginning of the set up to the current row, creating a cumulative or "running" total.
-
create coding exercises in C# with solutions
Of course! Here is a set of C# coding exercises with solutions, organized by difficulty. These exercises are designed to be run in a simple console application.
-
Setup: Create a new C# console project. You can do this easily with the .NET CLI:
dotnet new console -o CSharpExercises cd CSharpExercises
-
Running: Place the code for each solution inside the
Program.cs
file and run it from your terminal withdotnet run
.
These exercises cover basic syntax, control flow, and methods.
-
Task: Create a method that takes two numbers and a mathematical operator (
+
,-
,*
,/
) as input, and returns the result of the operation. Handle the case of division by zero. -
Concepts to Use: Methods, Parameters,
switch
statement, Return values.
Solution:
// Program.cs
using System;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Calculator Results:");
Console.WriteLine($"10 + 5 = {Calculate(10, 5, '+')}");
Console.WriteLine($"10 - 5 = {Calculate(10, 5, '-')}");
Console.WriteLine($"10 * 5 = {Calculate(10, 5, '*')}");
Console.WriteLine($"10 / 5 = {Calculate(10, 5, '/')}");
// Test division by zero
try
{
Calculate(10, 0, '/');
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
}
public static double Calculate(double num1, double num2, char operation)
{
switch (operation)
{
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
if (num2 == 0)
{
throw new ArgumentException("Error: Cannot divide by zero.");
}
return num1 / num2;
default:
throw new ArgumentException("Error: Invalid operator.");
}
}
}
Explanation:
- The
Calculate
method accepts twodouble
numbers and achar
for the operator. - A
switch
statement is used to efficiently check theoperation
character. - Each
case
performs the corresponding mathematical operation and usesreturn
to send the result back. - For division, we first check if
num2
is zero. If it is, wethrow
anArgumentException
to signal an error. This is better than returning a magic number like0
or-1
. - The
default
case handles any invalid operators, also throwing an exception.
- Task: Write a program that prints the numbers from 1 to 100. For multiples of three, print "Fizz" instead of the number. For multiples of five, print "Buzz". For numbers which are multiples of both three and five, print "FizzBuzz".
-
Concepts to Use:
for
loop,if-else if-else
statements, Modulo operator (%
).
Solution:
// Program.cs
using System;
public class Program
{
public static void Main(string[] args)
{
for (int i = 1; i <= 100; i++)
{
// Check for multiples of both 3 and 5 first, as this is the most specific condition.
if (i % 3 == 0 && i % 5 == 0) // or (i % 15 == 0)
{
Console.WriteLine("FizzBuzz");
}
else if (i % 3 == 0)
{
Console.WriteLine("Fizz");
}
else if (i % 5 == 0)
{
Console.WriteLine("Buzz");
}
else
{
Console.WriteLine(i);
}
}
}
}
Explanation:
- A
for
loop iterates from 1 to 100. - The modulo operator
%
gives the remainder of a division.i % 3 == 0
istrue
ifi
is perfectly divisible by 3. - The
if-else if-else
structure checks the conditions in order. It's important to check for the "FizzBuzz" case (i % 3 == 0 && i % 5 == 0
) first, because if we checked for "Fizz" first, it would print "Fizz" for the number 15 and never reach the "FizzBuzz" condition. - If none of the conditions are met, the
else
block simply prints the number itself.
-
Task: Create a
BankAccount
class. It should have properties forAccountNumber
(string),OwnerName
(string), andBalance
(decimal). The balance should only be modifiable throughDeposit
andWithdraw
methods. Ensure a withdrawal cannot result in a negative balance. - Concepts to Use: Classes, Properties, Constructors, Methods, Encapsulation.
Solution:
// Program.cs
using System;
public class BankAccount
{
// Properties
public string AccountNumber { get; } // Read-only after creation
public string OwnerName { get; set; }
public decimal Balance { get; private set; } // Read-only from outside, modifiable inside
// Constructor
public BankAccount(string accountNumber, string ownerName, decimal initialBalance)
{
AccountNumber = accountNumber;
OwnerName = ownerName;
Balance = initialBalance;
}
// Methods
public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentException("Deposit amount must be positive.", nameof(amount));
}
Balance += amount;
}
public void Withdraw(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentException("Withdrawal amount must be positive.", nameof(amount));
}
if (Balance - amount < 0)
{
throw new InvalidOperationException("Insufficient funds for this withdrawal.");
}
Balance -= amount;
}
}
public class Program
{
public static void Main(string[] args)
{
var account = new BankAccount("12345", "John Doe", 1000.00m);
Console.WriteLine($"Account {account.AccountNumber} for {account.OwnerName} has a balance of ${account.Balance}.");
account.Deposit(250.50m);
Console.WriteLine($"After deposit, balance is ${account.Balance}.");
account.Withdraw(500m);
Console.WriteLine($"After withdrawal, balance is ${account.Balance}.");
try
{
// This will fail
account.Withdraw(1000m);
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
// This also fails because the setter is private
// account.Balance = 5000; // <-- This line would cause a compile error
}
}
Explanation:
-
Encapsulation: The
Balance
property has aprivate set;
. This means its value can only be changed by code inside theBankAccount
class (i.e., by theDeposit
andWithdraw
methods). This prevents external code from setting an arbitrary balance. -
Constructor: The constructor is a special method that runs when a new object is created (
new BankAccount(...)
). It initializes the object's state. -
Read-only Property:
AccountNumber
has noset
accessor at all (after C# 9,init;
is also an option for constructors). This means its value is set once in the constructor and can never be changed again. - Business Logic: The methods enforce rules: deposits must be positive, and withdrawals cannot overdraw the account.
-
Task: Create a
Product
class withName
(string),Category
(string), andPrice
(decimal) properties. Create a list ofProduct
objects. Use LINQ to perform the following queries:- Find all products in the "Electronics" category.
- Find the first product with a price over $500.
- Order all products by price, from cheapest to most expensive.
-
Concepts to Use:
List<T>
, LINQ methods (Where
,FirstOrDefault
,OrderBy
).
Solution:
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
List<Product> products = new List<Product>
{
new Product { Name = "Laptop", Category = "Electronics", Price = 1200.50m },
new Product { Name = "Smartphone", Category = "Electronics", Price = 750.00m },
new Product { Name = "Desk Chair", Category = "Furniture", Price = 150.75m },
new Product { Name = "Coffee Maker", Category = "Appliances", Price = 80.00m },
new Product { Name = "Keyboard", Category = "Electronics", Price = 99.99m }
};
// 1. Find all products in the "Electronics" category
Console.WriteLine("--- Electronics Products ---");
var electronics = products.Where(p => p.Category == "Electronics");
foreach (var product in electronics)
{
Console.WriteLine($"{product.Name} (${product.Price})");
}
// 2. Find the first product with a price over $500
Console.WriteLine("\n--- First Product Over $500 ---");
var expensiveProduct = products.FirstOrDefault(p => p.Price > 500m);
if (expensiveProduct != null)
{
Console.WriteLine($"{expensiveProduct.Name} (${expensiveProduct.Price})");
}
// 3. Order all products by price
Console.WriteLine("\n--- Products Ordered by Price ---");
var orderedProducts = products.OrderBy(p => p.Price);
foreach (var product in orderedProducts)
{
Console.WriteLine($"{product.Name} (${product.Price})");
}
}
}
Explanation:
-
LINQ (Language Integrated Query): LINQ provides powerful methods to query collections.
using System.Linq;
is required. -
Where(p => p.Category == "Electronics")
: TheWhere
method filters a collection. It takes a lambda expressionp => ...
as an argument. For each productp
, it evaluates the condition. Iftrue
, the product is included in the result. -
FirstOrDefault(p => p.Price > 500m)
: This finds the first element that matches the condition. If no element matches, it returns the default value for the type (null
for objects), which prevents an exception. -
OrderBy(p => p.Price)
: This sorts the collection. The lambda expression specifies which property to sort by. By default, it sorts in ascending order. For descending, you would useOrderByDescending
.
-
Task: Write an asynchronous method that takes a URL as input, downloads the HTML content from that URL, and returns the character count of the content. Use
HttpClient
and theasync/await
pattern. -
Concepts to Use:
async/await
,Task<T>
,HttpClient
, Asynchronous programming.
Solution:
// Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class Program
{
// Main method must be async to use await at the top level
public static async Task Main(string[] args)
{
string url = "https://www.microsoft.com";
Console.WriteLine($"Downloading content from {url}...");
try
{
int charCount = await DownloadContentLengthAsync(url);
Console.WriteLine($"Successfully downloaded. Character count: {charCount:N0}");
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error downloading content: {e.Message}");
}
}
public static async Task<int> DownloadContentLengthAsync(string url)
{
// Using statement ensures the HttpClient is disposed of correctly
using (var client = new HttpClient())
{
// Asynchronously get the response string from the URL
string content = await client.GetStringAsync(url);
// Once the content is downloaded, return its length
return content.Length;
}
}
}
Explanation:
-
async
andawait
:- The
async
keyword on a method signature allows the use of theawait
keyword inside it. - The
await
keyword tells the program to pause the execution of this method until the awaited task (e.g.,client.GetStringAsync(url)
) is complete. - Crucially, it does not block the application's main thread. The thread is free to do other work (like updating a UI or handling other requests). When the download finishes, execution resumes where it left off.
- The
-
Task<int>
: The return type of anasync
method that returns a value isTask<T>
.Task<int>
represents a promise that this method will eventually produce anint
. When youawait
it, you "unwrap" the task to get theint
result. -
HttpClient
: This is the modern .NET class for making HTTP requests.GetStringAsync
is an asynchronous method that returns aTask<string>
. -
Main
Method: Since C# 7.1, theMain
method itself can beasync Task
, which allows you to useawait
at the top level of your program for clean, simple code.
create coding exercises in .Net with solutions
Of course! These exercises focus on building a .NET application, specifically an ASP.NET Core Web API, which is one of the most common use cases for .NET today. We'll progress from a simple in-memory API to one that uses a database with Entity Framework Core.
- Setup: You will need the .NET SDK installed. We'll use the .NET CLI to create and manage the project.
-
Tools:
- A code editor like Visual Studio Code or Visual Studio.
- A tool to test your API, like Postman, or the built-in Swagger UI that comes with the ASP.NET Core Web API template.
Initial Project Setup: Open your terminal and run the following commands:
# Create a new Web API project in a folder named "TodoApi"
dotnet new webapi -o TodoApi
# Navigate into the new project directory
cd TodoApi
This creates a boilerplate project with a sample "WeatherForecast" controller. We will ignore that and create our own.
Goal: Understand the basics of controllers, routing, and returning data.
-
Task: Create a
TodoItemsController
that manages a hardcoded list of to-do items. It should have one endpoint to get all to-do items.
Solution:
-
Create a Model: Inside your project, create a new file named
TodoItem.cs
.// TodoApi/TodoItem.cs public class TodoItem { public long Id { get; set; } public string? Name { get; set; } public bool IsComplete { get; set; } }
-
Create the Controller: Create a new folder named
Controllers
. Inside it, add a new file namedTodoItemsController.cs
.// TodoApi/Controllers/TodoItemsController.cs using Microsoft.AspNetCore.Mvc; [ApiController] // Marks this class as an API controller [Route("api/[controller]")] // Sets the route to /api/TodoItems public class TodoItemsController : ControllerBase { // A static list to act as our in-memory "database" private static readonly List<TodoItem> _inMemoryTodoItems = new List<TodoItem> { new TodoItem { Id = 1, Name = "Learn ASP.NET Core", IsComplete = true }, new TodoItem { Id = 2, Name = "Build an API", IsComplete = false } }; // GET: api/TodoItems [HttpGet] public ActionResult<IEnumerable<TodoItem>> GetTodoItems() { // ASP.NET Core automatically serializes this list to JSON return _inMemoryTodoItems; } }
-
Run and Test:
- Run the application from your terminal with
dotnet run
. - The output will show a URL like
https://localhost:7123
. - Open your browser or Postman and navigate to
https://localhost:7123/api/todoitems
. - You should see a JSON array of the two to-do items.
- Run the application from your terminal with
Explanation:
-
[ApiController]
: Enables helpful API-specific behaviors, like automatic HTTP 400 responses when the model state is invalid. -
[Route("api/[controller]")]
: This is a route template.[controller]
is a token that gets replaced by the controller's name minus the "Controller" suffix (so,TodoItems
). The final route is/api/TodoItems
. -
[HttpGet]
: This attribute marks theGetTodoItems
method as a handler for HTTP GET requests. -
ActionResult<T>
: This return type allows you to return either a simple type (IEnumerable<TodoItem>
) or anIActionResult
(likeNotFound()
,BadRequest()
, etc.). ASP.NET Core handles converting the C# object to a JSON response.
Goal: Replace the in-memory list with a real database using Entity Framework (EF) Core and enable getting a single item by its ID.
-
Task:
- Add EF Core to the project.
- Create a
DbContext
to manage the database connection. - Use Dependency Injection to provide the
DbContext
to the controller. - Modify the
GET
endpoint to pull from the database. - Add a new
GET
endpoint to retrieve a single to-do item by itsId
.
Solution:
-
Install EF Core Packages: Run these commands in your terminal in the project directory. We'll use SQLite for simplicity as it requires no external server setup.
dotnet add package Microsoft.EntityFrameworkCore.Sqlite dotnet add package Microsoft.EntityFrameworkCore.Design
-
Create the
DbContext
: Create a new file namedTodoContext.cs
.// TodoApi/TodoContext.cs using Microsoft.EntityFrameworkCore; public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } = null!; }
-
Configure Services in
Program.cs
: Modify yourProgram.cs
to register theDbContext
with the dependency injection container.// TodoApi/Program.cs (add these lines) using Microsoft.EntityFrameworkCore; // Add this using using TodoApi; // Add this using to access TodoContext var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // *** START: Add EF Core DbContext configuration *** builder.Services.AddDbContext<TodoContext>(opt => opt.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); // *** END: Add EF Core DbContext configuration *** // ... rest of the file
-
Add Connection String: In your
appsettings.json
file, add a connection string.// TodoApi/appsettings.json { "ConnectionStrings": { "DefaultConnection": "Data Source=Todo.db" }, // ... rest of the file }
-
Create the Database using Migrations:
- Run this command to create a migration file. This file contains code to create your database schema.
dotnet ef migrations add InitialCreate
- Run this command to apply the migration and create the
Todo.db
file.dotnet ef database update
- Run this command to create a migration file. This file contains code to create your database schema.
-
Update the Controller: Modify
TodoItemsController.cs
to use the injectedTodoContext
.// TodoApi/Controllers/TodoItemsController.cs using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; // Add this [ApiController] [Route("api/[controller]")] public class TodoItemsController : ControllerBase { private readonly TodoContext _context; // Field to hold the context // Constructor to receive the context via dependency injection public TodoItemsController(TodoContext context) { _context = context; } // GET: api/TodoItems [HttpGet] public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems() { // Asynchronously get all items from the database return await _context.TodoItems.ToListAsync(); } // GET: api/TodoItems/5 [HttpGet("{id}")] public async Task<ActionResult<TodoItem>> GetTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { // Return a 404 Not Found response if the item doesn't exist return NotFound(); } return todoItem; } }
Explanation:
-
Dependency Injection (DI): In the controller's constructor, we ask for a
TodoContext
. Because we registered it inProgram.cs
, the ASP.NET Core DI container knows how to create an instance and "inject" it for us. This decouples our controller from the database setup. -
async
andawait
: Database operations are I/O-bound and should always be done asynchronously to avoid blocking the server thread.ToListAsync()
andFindAsync()
are asynchronous EF Core methods. -
[HttpGet("{id}")]
: The{id}
is a route parameter. When a request comes to/api/TodoItems/5
, the5
is automatically mapped to theid
parameter of theGetTodoItem
method. -
NotFound()
: This is a helper method that returns a standard HTTP 404 response.
Goal: Complete the CRUD (Create, Read, Update, Delete) functionality of the API.
-
Task: Add
POST
,PUT
, andDELETE
endpoints to theTodoItemsController
.
Solution: Add the following methods to your TodoItemsController.cs
.
// Add these methods inside the TodoItemsController class
// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
// Return a 201 Created response with a Location header pointing to the new resource
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}
// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest(); // IDs in URL and body must match
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.TodoItems.Any(e => e.Id == id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent(); // Return 204 No Content on successful update
}
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
Explanation:
-
[HttpPost]
: This method handles creating new resources. ThetodoItem
is deserialized from the JSON request body. -
CreatedAtAction(...)
: This is a best-practice response for aPOST
request. It returns an HTTP 201Created
status. It also adds aLocation
header to the response, which contains the URL of the newly created item (e.g.,/api/TodoItems/3
). -
[HttpPut("{id}")]
: This method handles updating an existing resource. It's idempotent, meaning calling it multiple times with the same data should have the same result. -
_context.Entry(todoItem).State = EntityState.Modified;
: This tells EF Core to track the entity as "modified." WhenSaveChangesAsync
is called, EF Core will generate an SQLUPDATE
statement. -
[HttpDelete("{id}")]
: This method handles deleting a resource. We first find the item, and if it exists, we remove it from the context and save the changes. -
NoContent()
: A successfulPUT
orDELETE
typically returns an HTTP 204No Content
response, indicating success without sending back a response body.
create coding exercises for experienced in React with solutions
Of course. Here are several coding exercises designed for experienced React developers. These problems focus on performance, architecture, advanced hooks, and state management patterns. Each comes with a detailed solution and explanation.
Goal: Understand and apply performance optimization techniques to prevent unnecessary re-renders in a complex component.
-
Problem: You have a
DataGrid
component that renders a large list ofGridRow
components. The parentDataGrid
component also has a timer that updates its own state every second, causing the entire grid to re-render. EachGridRow
has a "Highlight" button that should highlight only that specific row. Your task is to optimize the component so that only the row that is actually highlighted re-renders, and the other rows do not re-render every time the parent's timer ticks. -
Key Concepts:
React.memo
,useCallback
,useState
.
Solution:
DataGrid.jsx
(The Parent Component)
import React, { useState, useEffect, useCallback } from 'react';
import GridRow from './GridRow';
// Helper function to generate some mock data
const generateGridData = (count) =>
Array.from({ length: count }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
value: Math.floor(Math.random() * 100),
}));
const initialData = generateGridData(50);
function DataGrid() {
const [data, setData] = useState(initialData);
const [highlightedRow, setHighlightedRow] = useState(null);
const [time, setTime] = useState(new Date());
// This timer will cause the DataGrid component to re-render every second
useEffect(() => {
const timerId = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timerId);
}, []);
// Use useCallback to memoize the highlight function.
// Without this, a new function would be created on every render,
// breaking the memoization of the GridRow component.
const handleHighlight = useCallback((rowId) => {
setHighlightedRow(rowId);
}, []); // Dependency array is empty because it doesn't depend on component state
return (
<div>
<h1>Performant Data Grid</h1>
<p>Parent Component Timer: {time.toLocaleTimeString()}</p>
<div style={{ border: '1px solid black', maxHeight: '400px', overflowY: 'scroll' }}>
{data.map((row) => (
<GridRow
key={row.id}
row={row}
isHighlighted={highlightedRow === row.id}
onHighlight={handleHighlight}
/>
))}
</div>
</div>
);
}
export default DataGrid;
GridRow.jsx
(The Optimized Child Component)
import React from 'react';
// This is the key: We wrap the component in React.memo
const GridRow = React.memo(function GridRow({ row, isHighlighted, onHighlight }) {
// This log will demonstrate whether the component re-renders
console.log(`Rendering row: ${row.name}`);
const style = {
padding: '8px',
borderBottom: '1px solid #eee',
backgroundColor: isHighlighted ? 'lightblue' : 'white',
};
return (
<div style={style}>
{row.name} - Value: {row.value}
<button onClick={() => onHighlight(row.id)} style={{ marginLeft: '10px' }}>
Highlight
</button>
</div>
);
});
export default GridRow;
Explanation:
-
The Problem: The
setInterval
inDataGrid
updates thetime
state every second. This forcesDataGrid
to re-render, which in turn causes it to re-render all of its children (GridRow
components). You can see this by putting aconsole.log
inGridRow
without any optimizations – it will log for all 50 rows every second. -
React.memo
: By wrappingGridRow
inReact.memo
, we tell React to perform a shallow comparison of its props. If the props (row
,isHighlighted
,onHighlight
) have not changed since the last render, React will skip re-rendering this component and reuse the last rendered result. -
useCallback
: This is the crucial second step.React.memo
alone is not enough. In theDataGrid
component, thehandleHighlight
function is defined inside the component body. This means a new instance of thehandleHighlight
function is created on every single render ofDataGrid
. When this new function is passed as theonHighlight
prop toGridRow
, the prop comparison byReact.memo
fails (becausefunction A !== function B
, even if they have the same code).useCallback(fn, deps)
returns a memoized version of the function that only changes if one of the dependencies in thedeps
array has changed. By usinguseCallback
with an empty dependency array[]
, we ensure that the same function instance is passed toGridRow
on every render, allowingReact.memo
to work correctly.
Goal: Build a scalable state management solution for a feature (like a shopping cart) without relying on external libraries like Redux.
-
Problem: You need to manage the state of a shopping cart. The state is an array of items, and there are multiple actions that can modify it:
ADD_ITEM
,REMOVE_ITEM
,UPDATE_QUANTITY
. Using multipleuseState
calls would be cumbersome and error-prone. Create a centralized state logic usinguseReducer
and provide it to multiple components usinguseContext
. -
Key Concepts:
useReducer
,useContext
, Complex state objects, Action dispatching.
Solution:
cartContext.js
(The Context and Reducer)
import React, { createContext, useReducer, useContext } from 'react';
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM': {
const existingItem = state.find((item) => item.id === action.payload.id);
if (existingItem) {
// If item exists, just update quantity
return state.map((item) =>
item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item
);
}
// Otherwise, add new item
return [...state, { ...action.payload, quantity: 1 }];
}
case 'REMOVE_ITEM': {
return state.filter((item) => item.id !== action.payload.id);
}
case 'UPDATE_QUANTITY': {
return state.map((item) =>
item.id === action.payload.id
? { ...item, quantity: Math.max(0, action.payload.quantity) }
: item
).filter(item => item.quantity > 0); // Also remove if quantity is 0
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, []);
return (
<CartStateContext.Provider value={state}>
<CartDispatchContext.Provider value={dispatch}>
{children}
</CartDispatchContext.Provider>
</CartStateContext.Provider>
);
};
// Custom hooks to make consuming the context cleaner
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
App.js
and Components
import React from 'react';
import { CartProvider, useCartState, useCartDispatch } from './cartContext';
const products = [
{ id: 1, name: 'React Cookbook', price: 35.00 },
{ id: 2, name: 'Advanced Hooks', price: 45.50 },
{ id: 3, name: 'State Management Guide', price: 29.99 },
];
function ProductList() {
const dispatch = useCartDispatch();
const handleAdd = (product) => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
return (
<div>
<h2>Products</h2>
{products.map((product) => (
<div key={product.id} style={{ marginBottom: '10px' }}>
{product.name} - ${product.price.toFixed(2)}
<button onClick={() => handleAdd(product)} style={{ marginLeft: '10px' }}>
Add to Cart
</button>
</div>
))}
</div>
);
}
function ShoppingCart() {
const cart = useCartState();
const dispatch = useCartDispatch();
const handleUpdate = (id, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
};
if (cart.length === 0) {
return <p>Your cart is empty.</p>;
}
const totalPrice = cart.reduce((total, item) => total + item.price * item.quantity, 0);
return (
<div>
<h2>Shopping Cart</h2>
{cart.map((item) => (
<div key={item.id}>
<span>{item.name}</span>
<input
type="number"
value={item.quantity}
onChange={(e) => handleUpdate(item.id, parseInt(e.target.value, 10))}
style={{ width: '50px', margin: '0 10px' }}
/>
<span>${(item.price * item.quantity).toFixed(2)}</span>
</div>
))}
<h3>Total: ${totalPrice.toFixed(2)}</h3>
</div>
);
}
export default function App() {
return (
<CartProvider>
<h1>useReducer + useContext Shopping Cart</h1>
<ProductList />
<hr />
<ShoppingCart />
</CartProvider>
);
}
Explanation:
-
Centralized Logic: All the logic for modifying the cart state lives in one place: the
cartReducer
. This makes the state transitions predictable and easy to debug. -
useReducer
: It is preferred overuseState
when you have complex state logic involving multiple sub-values or when the next state depends on the previous one. It takes a reducer function and an initial state, and returns the currentstate
and adispatch
function. -
dispatch
: Instead of calling multiplesetState
functions, components calldispatch({ type: 'ACTION_TYPE', payload: data })
. This "action" object is sent to the reducer, which then computes the new state. -
useContext
: We split the state and dispatch into two separate contexts. This is a common performance optimization. If a component only needsdispatch
(which never changes), it won't re-render when thestate
changes. TheCartProvider
wraps the application, making the state and dispatch function available to any child component. -
Clean Components: The
ProductList
andShoppingCart
components are clean. They don't contain any state logic themselves; they simply consume the context and dispatch actions. This is a great separation of concerns.
Goal: Abstract away repetitive data-fetching logic into a generic, reusable, and type-safe custom hook.
-
Problem: In many components, you need to fetch data from an API. The logic for handling loading, error, and data states is always the same. Create a custom hook
useFetch
that encapsulates this logic. It should be generic so it can be used with any API endpoint and data type. -
Key Concepts: Custom Hooks,
useEffect
with cleanup, Generics (TypeScript),AbortController
for handling unmounted fetches.
Solution:
useFetch.ts
(The Custom Hook - TypeScript is used for generics)
import { useState, useEffect } from 'react';
// Define the state structure for our hook
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
export function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
// AbortController is used to cancel the fetch if the component unmounts
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
// Set initial loading state before fetch
setState({ data: null, loading: true, error: null });
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
// Check if the request was aborted before setting state
if (!signal.aborted) {
setState({ data, loading: false, error: null });
}
} catch (error) {
// Don't update state if the error was due to an abort
if (error instanceof Error && error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setState({ data: null, loading: false, error: error as Error });
}
}
};
fetchData();
// Cleanup function: this is called when the component unmounts
return () => {
abortController.abort();
};
}, [url]); // Re-run the effect if the URL changes
return state;
}
UserProfile.tsx
(Component using the hook)
import React from 'react';
import { useFetch } from './useFetch';
// Define the type for our user data
interface User {
id: number;
name: string;
email: string;
}
function UserProfile({ userId }: { userId: number }) {
const { data: user, loading, error } = useFetch<User>(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
if (loading) {
return <div>Loading user profile...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
if (!user) {
return null;
}
return (
<div>
<h2>User Profile</h2>
<p><strong>ID:</strong> {user.id}</p>
<p><strong>Name:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
</div>
);
}
export default function App() {
return <UserProfile userId={1} />
}
Explanation:
-
Encapsulation: The
useFetch
hook hides all the complexity of fetching data. The component that uses it only needs to know the URL and how to handle the three possible states:loading
,error
, anddata
. -
Generics (
<T>
): By using TypeScript generics, we make the hook type-safe. When we calluseFetch<User>(...)
, TypeScript knows that thedata
property in the returned state will be of typeUser
ornull
. This provides excellent autocompletion and prevents type-related bugs. -
useEffect
Dependency: TheuseEffect
hasurl
in its dependency array. This means if theurl
prop ever changes, the hook will automatically re-fetch the data for the new URL. -
Cleanup with
AbortController
: This is a critical pattern for any asynchronous operation inuseEffect
. If the component unmounts while thefetch
is still in progress, we need to cancel it. Otherwise, React will throw an error whensetState
is called on an unmounted component. TheAbortController
provides a signal that can be passed tofetch
, and theabort()
method is called in theuseEffect
cleanup function.
create Angular coding exercises for experienced developer with solutions
Of course. Here are several coding exercises for experienced Angular developers. These problems go beyond the basics and focus on advanced concepts like architectural patterns, performance optimization, RxJS mastery, and dynamic components.
Goal: Build a robust, reactive, and scalable state management service (a "store") for a feature without relying on a library like NgRx.
-
Problem: You need to manage the state of a "products" feature. This includes a list of products, a loading indicator, an error state, and a selected product. Components should be able to react to state changes without manual change detection, and the state logic must be centralized and testable.
-
Key Concepts: RxJS
BehaviorSubject
,Observable
,shareReplay
,distinctUntilChanged
,map
, Service-based architecture ("store" pattern).
Solution:
products.service.ts
(The Reactive Store)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, tap, map, distinctUntilChanged, shareReplay } from 'rxjs/operators';
// Define the state structure
export interface Product {
id: number;
name: string;
price: number;
category: string;
}
export interface ProductsState {
products: Product[];
selectedProductId: number | null;
loading: boolean;
error: string | null;
}
// Initialize the state
let _state: ProductsState = {
products: [],
selectedProductId: null,
loading: false,
error: null,
};
@Injectable({
providedIn: 'root',
})
export class ProductsStoreService {
// A BehaviorSubject holds the current state value
private store = new BehaviorSubject<ProductsState>(_state);
// Expose the state as an Observable
private state$ = this.store.asObservable();
// --- Selectors: Public Observables for components to subscribe to ---
products$ = this.state$.pipe(
map(state => state.products),
distinctUntilChanged() // Only emit when the products array reference changes
);
selectedProduct$ = this.state$.pipe(
map(state => state.products.find(p => p.id === state.selectedProductId)),
distinctUntilChanged()
);
loading$ = this.state$.pipe(map(state => state.loading), distinctUntilChanged());
error$ = this.state$.pipe(map(state => state.error), distinctUntilChanged());
constructor(private http: HttpClient) {
// This makes the observable hot and caches the last emitted value
// for late subscribers.
this.state$.pipe(shareReplay(1)).subscribe();
}
// --- Actions: Methods to update the state ---
public loadProducts() {
this.updateState({ ..._state, loading: true, error: null });
this.http.get<Product[]>('/api/products').pipe( // Use a mock API URL
tap(products => {
this.updateState({ ..._state, products, loading: false });
}),
catchError(err => {
this.updateState({ ..._state, error: 'Failed to load products', loading: false });
return throwError(() => err);
})
).subscribe();
}
public selectProduct(productId: number) {
this.updateState({ ..._state, selectedProductId: productId });
}
// --- Private state mutation ---
private updateState(newState: ProductsState) {
_state = newState;
this.store.next(_state);
}
}
product-list.component.ts
(Consuming Component)
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { ProductsStoreService } from './products.service';
import { Observable } from 'rxjs';
import { Product } from './products.service';
@Component({
selector: 'app-product-list',
template: `
<h2>Products</h2>
<div *ngIf="loading$ | async">Loading...</div>
<div *ngIf="error$ | async as error" style="color: red;">{{ error }}</div>
<ul>
<li *ngFor="let product of (products$ | async)" (click)="selectProduct(product.id)">
{{ product.name }}
</li>
</ul>
`,
changeDetection: ChangeDetectionStrategy.OnPush, // Crucial for performance
})
export class ProductListComponent implements OnInit {
products$: Observable<Product[]>;
loading$: Observable<boolean>;
error$: Observable<string | null>;
constructor(private store: ProductsStoreService) {
this.products$ = this.store.products$;
this.loading$ = this.store.loading$;
this.error$ = this.store.error$;
}
ngOnInit() {
this.store.loadProducts();
}
selectProduct(id: number) {
this.store.selectProduct(id);
}
}
Explanation:
-
BehaviorSubject
as the Store: ABehaviorSubject
is perfect for a state store because it holds the "current" value and emits it to any new subscribers. -
Selectors (
products$
,loading$
): We expose specific slices of the state as separate observables. This allows components to subscribe only to the data they need. -
distinctUntilChanged()
: This operator is a key performance optimization. It ensures that the observable only emits a new value if it's different from the last one, preventing unnecessary re-renders in components. -
Immutability: The
updateState
method creates a new state object ({ ..._state, ... }
) every time. This is critical fordistinctUntilChanged
to work correctly with object references and for Angular's change detection to function predictably. -
Actions (
loadProducts
): Public methods on the service act as "actions" that orchestrate state changes. All state mutations are centralized within the service. -
ChangeDetectionStrategy.OnPush
: By using theasync
pipe, our component becomes purely reactive. Setting its change detection toOnPush
tells Angular it only needs to re-render when its@Input()
properties change or an event it's subscribed to (via theasync
pipe) emits a new value. This provides a significant performance boost.
Goal: Create a system that can dynamically render different components into a container based on configuration or user input, without using *ngIf
or *ngSwitch
.
-
Problem: You are building a dashboard where users can add different types of widgets (e.g., a
ChartWidgetComponent
, aTableWidgetComponent
). The list of available widgets is defined in a configuration object. You need to create aWidgetHostDirective
and aDashboardComponent
that can render the correct widget component into the view at runtime. -
Key Concepts:
ViewContainerRef
,ComponentFactoryResolver
(pre-Angular 13) /createComponent
(Angular 13+), Directives,@Input
.
Solution:
widget-host.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appWidgetHost]',
})
export class WidgetHostDirective {
// The directive injects ViewContainerRef to get access to the view container
// of the element that will host the dynamically added component.
constructor(public viewContainerRef: ViewContainerRef) {}
}
widget.interface.ts
// An interface that our dynamic components will implement
export interface WidgetComponent {
data: any;
}
Widget Components (chart-widget.component.ts
, table-widget.component.ts
)
import { Component, Input } from '@angular/core';
import { WidgetComponent } from './widget.interface';
@Component({
template: `<h4>Chart Widget</h4><p>Data: {{ data?.source }}</p>`,
})
export class ChartWidgetComponent implements WidgetComponent {
@Input() data: any;
}
@Component({
template: `<h4>Table Widget</h4><pre>{{ data | json }}</pre>`,
})
export class TableWidgetComponent implements WidgetComponent {
@Input() data: any;
}
dashboard.component.ts
import { Component, OnInit, ViewChild, Type, OnDestroy } from '@angular/core';
import { WidgetHostDirective } from './widget-host.directive';
import { ChartWidgetComponent } from './chart-widget.component';
import { TableWidgetComponent } from './table-widget.component';
import { WidgetComponent } from './widget.interface';
// Configuration mapping a widget type string to a component class
const widgetMap: { [key: string]: Type<WidgetComponent> } = {
chart: ChartWidgetComponent,
table: TableWidgetComponent,
};
@Component({
selector: 'app-dashboard',
template: `
<h2>Dashboard</h2>
<select (change)="loadWidget($event)">
<option value="">-- Select a Widget --</option>
<option value="chart">Chart</option>
<option value="table">Table</option>
</select>
<!-- This is the container where the dynamic component will be inserted -->
<ng-template appWidgetHost></ng-template>
`,
})
export class DashboardComponent {
@ViewChild(WidgetHostDirective, { static: true }) widgetHost!: WidgetHostDirective;
loadWidget(event: Event) {
const selectedType = (event.target as HTMLSelectElement).value;
if (!selectedType) return;
const componentType = widgetMap[selectedType];
// Get the view container from our directive
const viewContainerRef = this.widgetHost.viewContainerRef;
viewContainerRef.clear(); // Clear any previously rendered component
// Create the component and insert it into the view container
const componentRef = viewContainerRef.createComponent(componentType);
// Pass data to the newly created component
componentRef.instance.data = {
source: selectedType === 'chart' ? 'Sales Data 2023' : { rows: 10, cols: 5 }
};
}
}
Explanation:
-
WidgetHostDirective
: This directive's sole purpose is to provide a "handle" to a specific location in the template's DOM. By injectingViewContainerRef
, it gives us programmatic access to that element's container. -
@ViewChild
: In theDashboardComponent
, we use@ViewChild
to get an instance of ourWidgetHostDirective
from the template. -
viewContainerRef.createComponent()
: This is the core of dynamic component loading in modern Angular. It takes a component class (not a selector string) and creates an instance of it, attaching it to the view container. This method returns aComponentRef
, which gives you access to the component's instance, allowing you to set@Input
properties or subscribe to@Output
events. -
Configuration (
widgetMap
): Using a map to link a string identifier to a componentType
makes the system extensible. To add a new widget, you only need to create the component and add it to the map. -
ng-template
: We apply the directive to an<ng-template>
because it's a lightweight, "commented-out" element that serves as a perfect anchor point without adding any extra elements to the DOM itself.
Goal: Understand the inner workings of structural directives by creating a custom one that combines the functionality of *ngIf
and provides a context variable.
-
Problem: You often find yourself needing to fetch data and then check if it exists before rendering a template, while also providing the data to that template. The standard
*ngIf="data$ | async as data"
is good, but let's create a custom directive called*appIfData
that explicitly handles anull
orundefined
state and provides the data as a context variable. -
Key Concepts:
Structural Directives
,TemplateRef
,ViewContainerRef
,Input
,Directive Context
.
Solution:
if-data.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
// Define the shape of our directive's context object
interface IfDataContext<T> {
appIfData: T;
$implicit: T; // This allows us to use "let data" syntax
}
@Directive({
selector: '[appIfData]',
})
export class IfDataDirective<T> {
private hasView = false;
// We need to inject TemplateRef (what to stamp) and ViewContainerRef (where to stamp it)
constructor(
private templateRef: TemplateRef<IfDataContext<T>>,
private viewContainer: ViewContainerRef
) {}
// The 'set' allows us to run logic whenever the input value changes
@Input() set appIfData(data: T) {
// Condition to check if data is "truthy" enough to render
const condition = data !== null && data !== undefined;
if (condition && !this.hasView) {
// If we should render but haven't yet, create the view
this.viewContainer.createEmbeddedView(this.templateRef, {
appIfData: data,
$implicit: data,
});
this.hasView = true;
} else if (!condition && this.hasView) {
// If we shouldn't render but a view exists, clear it
this.viewContainer.clear();
this.hasView = false;
}
}
}
app.component.ts
import { Component } from '@angular/core';
import { of, Observable } from 'rxjs';
import { delay } from 'rxjs/operators';
@Component({
selector: 'app-root',
template: `
<h2>Custom Structural Directive Demo</h2>
<button (click)="loadData()">Load Data</button>
<button (click)="clearData()">Clear Data</button>
<!-- Using the custom directive -->
<div *appIfData="user$ | async; let user">
<h3>User Details Loaded:</h3>
<p>Name: {{ user.name }}</p>
<p>ID: {{ user.id }}</p>
</div>
<!-- A message to show it's not rendered -->
<div *ngIf="!(user$ | async)">
<p>No user data to display. Click "Load Data".</p>
</div>
`,
})
export class AppComponent {
user$: Observable<{ id: number; name: string } | null> = of(null);
loadData() {
this.user$ = of({ id: 1, name: 'John Doe' }).pipe(delay(1000));
}
clearData() {
this.user$ = of(null);
}
}
Explanation:
-
TemplateRef
andViewContainerRef
: These are the two fundamental building blocks for a structural directive.-
TemplateRef
: Represents the embedded template that the directive is attached to (the<div>
in our example). -
ViewContainerRef
: Represents the container in the DOM where the template will be rendered.
-
-
Input Setter: By using a
set
on the@Input()
, we can execute logic every time the data bound to[appIfData]
changes. This is where we decide whether to create or destroy the embedded view. -
createEmbeddedView
: This method onViewContainerRef
"stamps out" the template. Crucially, its second argument is a context object. -
Directive Context: The properties of the context object become available as template variables.
- The
$implicit
property is special. It allows thelet user
syntax without specifying a key (e.g.,let user = data
). - We also provide a named property
appIfData
, which would allow for thelet user = appIfData
syntax if desired.
- The
create SQL coding exercises for experienced developer with solutions
Of course. Here is a set of advanced SQL coding exercises designed for experienced developers. These problems focus on complex joins, window functions, common table expressions (CTEs), recursive queries, and performance considerations.
All exercises will use the following schema for a more complex business scenario involving employees, departments, projects, and salaries. This setup code can be run in PostgreSQL or adapted for other modern SQL databases.
SQL Setup Code:
-- Create Tables
CREATE TABLE Departments (
department_id INT PRIMARY KEY,
department_name VARCHAR(50) NOT NULL
);
CREATE TABLE Employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
hire_date DATE NOT NULL,
department_id INT,
manager_id INT,
FOREIGN KEY (department_id) REFERENCES Departments(department_id),
FOREIGN KEY (manager_id) REFERENCES Employees(employee_id)
);
CREATE TABLE Projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100) NOT NULL,
start_date DATE,
end_date DATE
);
CREATE TABLE Employee_Projects (
employee_id INT,
project_id INT,
PRIMARY KEY (employee_id, project_id),
FOREIGN KEY (employee_id) REFERENCES Employees(employee_id),
FOREIGN KEY (project_id) REFERENCES Projects(project_id)
);
CREATE TABLE Salaries (
employee_id INT,
salary DECIMAL(10, 2) NOT NULL,
from_date DATE NOT NULL,
to_date DATE NOT NULL,
PRIMARY KEY (employee_id, from_date),
FOREIGN KEY (employee_id) REFERENCES Employees(employee_id)
);
-- Insert Sample Data
INSERT INTO Departments (department_id, department_name) VALUES
(1, 'Engineering'),
(2, 'Human Resources'),
(3, 'Sales'),
(4, 'Marketing');
INSERT INTO Employees (employee_id, first_name, last_name, email, hire_date, department_id, manager_id) VALUES
(1, 'James', 'Smith', '[email protected]', '2018-06-10', 1, NULL),
(2, 'Maria', 'Garcia', '[email protected]', '2019-03-15', 1, 1),
(3, 'David', 'Johnson', '[email protected]', '2019-08-21', 1, 1),
(4, 'Patricia', 'Miller', '[email protected]', '2020-01-30', 2, NULL),
(5, 'Robert', 'Davis', '[email protected]', '2020-05-12', 3, NULL),
(6, 'Linda', 'Rodriguez', '[email protected]', '2021-11-01', 3, 5),
(7, 'Michael', 'Wilson', '[email protected]', '2022-02-18', 1, 2),
(8, 'Susan', 'Moore', '[email protected]', '2022-07-22', 4, NULL);
INSERT INTO Projects (project_id, project_name, start_date, end_date) VALUES
(101, 'Phoenix Project', '2021-01-01', '2023-12-31'),
(102, 'Orion Initiative', '2022-05-01', '2024-06-30'),
(103, 'Marketing Campaign Q3', '2022-07-01', '2022-09-30');
INSERT INTO Employee_Projects (employee_id, project_id) VALUES
(1, 101), (1, 102),
(2, 101),
(3, 102),
(7, 101), (7, 102),
(8, 103);
INSERT INTO Salaries (employee_id, salary, from_date, to_date) VALUES
(1, 120000, '2018-06-10', '2020-12-31'),
(1, 135000, '2021-01-01', '9999-01-01'), -- Current salary
(2, 90000, '2019-03-15', '9999-01-01'),
(3, 92000, '2019-08-21', '9999-01-01'),
(4, 80000, '2020-01-30', '9999-01-01'),
(5, 110000, '2020-05-12', '9999-01-01'),
(6, 85000, '2021-11-01', '9999-01-01'),
(7, 95000, '2022-02-18', '9999-01-01'),
(8, 75000, '2022-07-22', '9999-01-01');
Note: The 9999-01-01
date is a common convention to represent "current" or "to date" in a temporal table.
Goal: Combine aggregations with window functions to compare individual salaries to departmental averages.
-
Task: For each employee, display their full name, department name, their current salary, and the average current salary for their department. Also include a column showing the difference between their salary and the department average.
-
Key Concepts: CTEs, Window Functions (
AVG() OVER (PARTITION BY ...)
), Joins.
Solution:
WITH CurrentSalaries AS (
-- Step 1: Isolate the current salary for each employee
SELECT
employee_id,
salary
FROM Salaries
WHERE to_date = '9999-01-01'
),
DepartmentalAvg AS (
-- Step 2: Calculate average salary per department using a window function
SELECT
e.employee_id,
e.first_name,
e.last_name,
d.department_name,
cs.salary,
AVG(cs.salary) OVER (PARTITION BY e.department_id) AS avg_department_salary
FROM Employees AS e
JOIN Departments AS d ON e.department_id = d.department_id
JOIN CurrentSalaries AS cs ON e.employee_id = cs.employee_id
)
-- Step 3: Calculate the difference and present the final result
SELECT
first_name,
last_name,
department_name,
salary,
ROUND(avg_department_salary, 2) AS avg_department_salary,
ROUND(salary - avg_department_salary, 2) AS salary_difference
FROM DepartmentalAvg
ORDER BY department_name, salary DESC;
Explanation:
- The
CurrentSalaries
CTE filters theSalaries
table to get only the active salary records. This is crucial for temporal tables. - The
DepartmentalAvg
CTE joins the employees, departments, and current salaries. - The key is the window function
AVG(cs.salary) OVER (PARTITION BY e.department_id)
.-
PARTITION BY e.department_id
tells theAVG
function to only consider rows within the same department for its calculation. - Unlike
GROUP BY
, a window function calculates the average for each partition but returns a value for every row, allowing us to compare the individual row's salary to the calculated average for its group.
-
- The final
SELECT
statement performs the simple subtraction and formatting.
Goal: Traverse a self-referencing relationship to build a hierarchical view.
-
Task: Write a query that displays the full organizational hierarchy, showing each employee, their manager, and their level in the hierarchy (e.g., CEO is level 1, their direct reports are level 2, etc.).
-
Key Concepts: Recursive CTEs.
Solution:
WITH RECURSIVE EmployeeHierarchy AS (
-- 1. Anchor Member: Select the top-level employees (those with no manager)
SELECT
employee_id,
first_name,
last_name,
manager_id,
1 AS hierarchy_level,
CAST(first_name || ' ' || last_name AS VARCHAR(255)) AS path
FROM Employees
WHERE manager_id IS NULL
UNION ALL
-- 2. Recursive Member: Join back to the CTE itself
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.manager_id,
h.hierarchy_level + 1,
CAST(h.path || ' -> ' || e.first_name || ' ' || e.last_name AS VARCHAR(255))
FROM Employees AS e
JOIN EmployeeHierarchy AS h ON e.manager_id = h.employee_id
)
SELECT
h.employee_id,
h.hierarchy_level,
h.first_name || ' ' || h.last_name AS employee_name,
mgr.first_name || ' ' || mgr.last_name AS manager_name,
h.path
FROM EmployeeHierarchy h
LEFT JOIN Employees mgr ON h.manager_id = mgr.employee_id
ORDER BY h.path;
Explanation:
-
WITH RECURSIVE
: This syntax initiates a recursive CTE. -
Anchor Member: This is the non-recursive part of the query. It selects the "base case"—the employees at the top of the hierarchy (where
manager_id IS NULL
). We initializehierarchy_level
to 1. Thepath
column is created to visualize the hierarchy. -
UNION ALL
: This combines the results of the anchor and recursive members. -
Recursive Member: This part of the query references the CTE itself (
EmployeeHierarchy
). It joins theEmployees
table back to the results of the previous iteration (h
). For each employeee
whose managere.manager_id
is already in the hierarchyh
, it adds them to the result set, incrementing thehierarchy_level
and appending their name to thepath
. - The final
SELECT
joins the hierarchy results with theEmployees
table again (a self-join) to get the manager's name for display purposes.
Goal: Identify complex relationships between records based on date ranges.
-
Task: Write a query to find all pairs of projects that have overlapping time periods. For each pair, show the project names and their start and end dates. Avoid listing the same pair twice (e.g., A-B and B-A) and don't list a project overlapping with itself.
-
Key Concepts: Self-join, Complex
ON
clause logic for date ranges.
Solution:
SELECT
p1.project_name AS project_1,
p1.start_date AS project_1_start,
p1.end_date AS project_1_end,
p2.project_name AS project_2,
p2.start_date AS project_2_start,
p2.end_date AS project_2_end
FROM Projects p1
JOIN Projects p2 ON
-- Condition 1: Ensure we don't compare a project with itself or list pairs twice.
p1.project_id < p2.project_id
AND
-- Condition 2: The core overlap logic.
-- Project 1's start date must be before Project 2's end date,
-- AND Project 1's end date must be after Project 2's start date.
p1.start_date <= p2.end_date AND p1.end_date >= p2.start_date;
Explanation:
- The query performs a self-join on the
Projects
table, allowing us to compare every project with every other project. -
p1.project_id < p2.project_id
: This is a simple but powerful trick. By only joining where the ID of the first project is less than the second, we automatically solve two problems:- A project cannot be compared with itself (since
p1.project_id
can't be less thanp1.project_id
). - Duplicate pairs are eliminated. We will get the pair (101, 102) but not (102, 101), because
102 < 101
is false.
- A project cannot be compared with itself (since
-
The Overlap Logic: The condition
p1.start_date <= p2.end_date AND p1.end_date >= p2.start_date
is the standard, most robust way to check for any kind of date range overlap. It covers all cases (one range containing another, partial overlaps at the beginning or end, etc.).
Goal: Solve a common and complex analytical query: finding the top N records within different groups.
-
Task: Find the top 2 highest-paid employees in each department based on their current salary. If there are ties in salary, list all tied employees.
-
Key Concepts: Window Functions (
RANK()
orDENSE_RANK()
), CTEs.
Solution:
WITH CurrentSalaries AS (
SELECT
employee_id,
salary
FROM Salaries
WHERE to_date = '9999-01-01'
),
RankedSalaries AS (
SELECT
e.employee_id,
e.first_name,
e.last_name,
d.department_name,
cs.salary,
-- Use RANK() or DENSE_RANK() to assign a rank within each department
RANK() OVER (PARTITION BY d.department_id ORDER BY cs.salary DESC) as salary_rank
FROM Employees e
JOIN Departments d ON e.department_id = d.department_id
JOIN CurrentSalaries cs ON e.employee_id = cs.employee_id
)
SELECT
employee_id,
first_name,
last_name,
department_name,
salary,
salary_rank
FROM RankedSalaries
WHERE salary_rank <= 2
ORDER BY department_name, salary_rank;
Explanation:
- We first create the
CurrentSalaries
CTE as before to isolate the relevant salary data. - The
RankedSalaries
CTE is where the magic happens.-
RANK() OVER (PARTITION BY d.department_id ORDER BY cs.salary DESC)
: This window function is the ideal tool for this problem.-
PARTITION BY d.department_id
: This divides the employees into groups by department. TheRANK()
function will restart its counting for each new department. -
ORDER BY cs.salary DESC
: Within each department, it orders the employees by salary from highest to lowest. -
RANK()
: Assigns a rank based on the ordering.RANK()
will skip numbers if there are ties (e.g., 1, 1, 3), whileDENSE_RANK()
would not (e.g., 1, 1, 2). For this problem, either is acceptable, butRANK()
is often used to show that a tie occurred.
-
-
- The final query is a simple
SELECT
from theRankedSalaries
CTE, filtering for rows where the calculatedsalary_rank
is less than or equal to 2.
create C# coding exercises for experienced developer with solutions
Of course. Here are several C# coding exercises designed for experienced developers. These problems go beyond basic syntax and focus on advanced language features, design patterns, asynchronous programming, and performance.
Goal: Master advanced async/await
patterns, including concurrent processing and handling throttling.
-
Problem: You have a list of URLs that need to be processed. The processing involves two steps: (1) downloading the content of the URL, and (2) performing a CPU-intensive analysis on the content (e.g., counting specific words). Your task is to build a
DataProcessor
that can process these URLs concurrently to improve performance, but with a configurable degree of parallelism to avoid overwhelming the network or CPU. -
Key Concepts:
async/await
,Task.WhenAll
,SemaphoreSlim
for throttling,HttpClientFactory
for managingHttpClient
.
Solution:
Setup: This exercise is best suited for a console application. You'll need to add the Microsoft.Extensions.Http
package for HttpClientFactory
.
dotnet add package Microsoft.Extensions.Http
DataProcessor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class DataProcessor
{
private readonly IHttpClientFactory _httpClientFactory;
public DataProcessor(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task ProcessUrlsAsync(IEnumerable<string> urls, int maxConcurrency)
{
// SemaphoreSlim is a lightweight semaphore used to limit concurrency.
using var semaphore = new SemaphoreSlim(maxConcurrency);
var processingTasks = new List<Task>();
foreach (var url in urls)
{
// Wait until a "slot" is available in the semaphore.
await semaphore.WaitAsync();
// Start the processing task and add it to our list.
// We don't await it here, allowing multiple tasks to run concurrently.
processingTasks.Add(Task.Run(async () =>
{
try
{
Console.WriteLine($"[START] Processing {url}");
string content = await DownloadContentAsync(url);
int wordCount = PerformCpuIntensiveAnalysis(content);
Console.WriteLine($"[END] {url} - Word count: {wordCount}");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Processing {url}: {ex.Message}");
}
finally
{
// Release the semaphore slot so another task can start.
semaphore.Release();
}
}));
}
// Wait for all the started processing tasks to complete.
await Task.WhenAll(processingTasks);
Console.WriteLine("All URLs processed.");
}
private async Task<string> DownloadContentAsync(string url)
{
var client = _httpClientFactory.CreateClient();
// Add a timeout to prevent hanging on unresponsive servers
client.Timeout = TimeSpan.FromSeconds(15);
return await client.GetStringAsync(url);
}
private int PerformCpuIntensiveAnalysis(string content)
{
// Simulate a CPU-bound operation
Thread.Sleep(TimeSpan.FromMilliseconds(new Random().Next(100, 500)));
return content.Split(new[] { ' ', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Count(word => word.Equals("the", StringComparison.OrdinalIgnoreCase));
}
}
Program.cs
(Usage)
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class Program
{
public static async Task Main(string[] args)
{
// Setup Dependency Injection for HttpClientFactory
var serviceProvider = new ServiceCollection()
.AddHttpClient()
.BuildServiceProvider();
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var processor = new DataProcessor(httpClientFactory);
var urls = new List<string>
{
"https://www.microsoft.com",
"https://www.google.com",
"https://www.amazon.com",
"https://www.apple.com",
"https://www.facebook.com",
"https://www.netflix.com",
"https://www.example.com", // This is fast
"http://thishostdoesnotexist.fail" // This will fail
};
// Process with a concurrency level of 3
await processor.ProcessUrlsAsync(urls, 3);
}
}
Explanation:
-
SemaphoreSlim
for Throttling:SemaphoreSlim
is initialized with amaxConcurrency
count. Theawait semaphore.WaitAsync()
call acts as a gate. If the semaphore's count is above zero, it decrements the count and continues immediately. If the count is zero, it asynchronously waits until another task callssemaphore.Release()
. This effectively limits the number of concurrently runningTask.Run
blocks to the specified maximum. -
Fire-and-Forget (with
Task.WhenAll
): We loop through the URLs and start tasks without awaiting them individually. This "fires off" the tasks. We store theseTask
objects in a list.Task.WhenAll(processingTasks)
then asynchronously waits for every task in the list to finish, ensuring the entire operation is complete beforeProcessUrlsAsync
returns. -
IHttpClientFactory
: UsingIHttpClientFactory
(via dependency injection) is the modern, recommended way to manageHttpClient
instances. It handles the lifetime of underlyingHttpMessageHandler
s, preventing common issues like socket exhaustion. -
Error Handling per Task: The
try...catch
block is inside theTask.Run
delegate. This ensures that an exception in one URL's processing does not crash the entire operation. The other tasks can continue to run. -
finally
block: Thesemaphore.Release()
is placed in afinally
block to guarantee that the semaphore slot is released, even if an exception occurs during processing.
Goal: Design and implement a reusable, generic data access layer that supports transactional operations.
-
Problem: In a data-driven application, you often repeat the same CRUD (Create, Read, Update, Delete) logic for different entities. You also need to ensure that multiple database operations can be committed or rolled back together as a single transaction. Create a generic
IRepository<T>
interface, a concreteRepository<T>
implementation using Entity Framework Core, and aUnitOfWork
class to manage transactions. -
Key Concepts: Generic Types (
<T>
), Repository Pattern, Unit of Work Pattern, Entity Framework CoreDbContext
, Dependency Injection.
Solution:
Setup: Create an ASP.NET Core Web API project or a Console App. Add the required EF Core packages.
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory # For easy testing
Interfaces.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
public interface IRepository<TEntity> where TEntity : class
{
Task<TEntity?> GetByIdAsync(object id);
Task<IEnumerable<TEntity>> GetAllAsync();
Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate);
Task AddAsync(TEntity entity);
void Remove(TEntity entity);
}
public interface IUnitOfWork : IDisposable
{
// A repository for each aggregate root in your domain
IRepository<Product> Products { get; }
IRepository<Order> Orders { get; }
Task<int> CommitAsync();
}
Note: We would define Product
and Order
classes elsewhere.
Repository.cs
and UnitOfWork.cs
(Implementation)
using Microsoft.EntityFrameworkCore;
using System.Linq;
// ... plus other usings from Interfaces.cs
// A sample DbContext
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; } = null!;
public DbSet<Order> Orders { get; set; } = null!;
}
// Generic Repository Implementation
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext _context;
protected readonly DbSet<TEntity> _dbSet;
public Repository(DbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity) => await _dbSet.AddAsync(entity);
public async Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate) => await _dbSet.Where(predicate).ToListAsync();
public async Task<IEnumerable<TEntity>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task<TEntity?> GetByIdAsync(object id) => await _dbSet.FindAsync(id);
public void Remove(TEntity entity) => _dbSet.Remove(entity);
}
// Unit of Work Implementation
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private bool _disposed = false;
// Repositories are lazily instantiated
private IRepository<Product>? _products;
private IRepository<Order>? _orders;
public IRepository<Product> Products => _products ??= new Repository<Product>(_context);
public IRepository<Order> Orders => _orders ??= new Repository<Order>(_context);
public UnitOfWork(AppDbContext context)
{
_context = context;
}
public async Task<int> CommitAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
_context.Dispose();
}
_disposed = true;
}
}
Note: Product
and Order
are placeholder entity classes.
Usage (e.g., in a Service or Controller)
public class OrderProcessingService
{
private readonly IUnitOfWork _unitOfWork;
public OrderProcessingService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task CreateOrderAsync(int productId, int quantity)
{
using (_unitOfWork)
{
var product = await _unitOfWork.Products.GetByIdAsync(productId);
if (product == null) throw new Exception("Product not found.");
if (product.Stock < quantity) throw new Exception("Insufficient stock.");
// 1. Update Product stock
product.Stock -= quantity;
// 2. Create a new Order
var newOrder = new Order { ProductId = productId, Quantity = quantity, OrderDate = DateTime.UtcNow };
await _unitOfWork.Orders.AddAsync(newOrder);
// Commit both changes as a single transaction
await _unitOfWork.CommitAsync();
}
}
}
Explanation:
-
Generic
IRepository<T>
: This interface defines a standard contract for data access operations. Using generics (<TEntity>
) means we don't have to write a new interface forProducts
,Orders
, etc. -
Repository<T>
Implementation: This class provides a concrete implementation using EF Core'sDbSet<T>
. It can work with any entity class (where TEntity : class
). -
IUnitOfWork
: This acts as a facade over theDbContext
. It exposes properties for each repository (Products
,Orders
) and, most importantly, a singleCommitAsync
method. This centralizes the call toDbContext.SaveChangesAsync()
. -
Transactional Integrity: In
OrderProcessingService
, two separate operations happen: updating a product's stock and creating a new order. By wrapping them in ausing
block for theUnitOfWork
and callingCommitAsync
only once at the end, we ensure both operations either succeed together or fail together. EF Core'sSaveChanges
automatically wraps its operations in a database transaction. -
Decoupling: The service layer (
OrderProcessingService
) depends on theIUnitOfWork
andIRepository
abstractions, not on the concrete EF Core implementation. This makes the application easier to test (you can mock the interfaces) and potentially switch data access technologies in the future.
Goal: Understand the power and pitfalls of reflection by building a simple object-to-object mapper.
-
Problem: While libraries like AutoMapper are feature-rich, it's a great exercise to understand how they work under the hood. Create a static
SimpleMapper
class that has one method,Map<TSource, TDestination>(TSource source)
, which creates a new instance ofTDestination
and copies the values of properties from thesource
object to thedestination
object where the property names and types match. -
Key Concepts: Reflection (
typeof
,GetProperties
), Generics, Caching with Dictionaries for performance.
Solution:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public static class SimpleMapper
{
// Cache the mapping functions to avoid repeated reflection, which is slow.
// ConcurrentDictionary is used for thread safety.
private static readonly ConcurrentDictionary<Tuple<Type, Type>, Action<object, object>> _mapperCache = new();
public static TDestination Map<TSource, TDestination>(TSource source)
where TDestination : new() // Constraint: Destination must have a parameterless constructor
{
if (source == null)
{
return default!;
}
var destination = new TDestination();
var mappingAction = GetOrCreateMappingAction(typeof(TSource), typeof(TDestination));
mappingAction(source, destination);
return destination;
}
private static Action<object, object> GetOrCreateMappingAction(Type sourceType, Type destinationType)
{
return _mapperCache.GetOrAdd(Tuple.Create(sourceType, destinationType), _ =>
{
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead)
.ToDictionary(p => p.Name);
var destProps = destinationType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite);
var actions = new List<Action<object, object>>();
foreach (var destProp in destProps)
{
if (sourceProps.TryGetValue(destProp.Name, out var sourceProp) && destProp.PropertyType == sourceProp.PropertyType)
{
// Create a specific action for this property pair
actions.Add((src, dest) =>
{
var value = sourceProp.GetValue(src);
destProp.SetValue(dest, value);
});
}
}
// Combine all property-setting actions into a single delegate
return (src, dest) =>
{
foreach (var action in actions)
{
action(src, dest);
}
};
});
}
}
Usage
public class User
{
public int Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; }
}
public class UserViewModel
{
public int Id { get; set; }
public string FullName { get; set; } = string.Empty; // This won't be mapped
public int Age { get; set; } // This won't be mapped
public string FirstName { get; set; } = string.Empty; // This will be mapped
}
public class Program
{
public static void Main(string[] args)
{
var user = new User { Id = 1, FirstName = "John", LastName = "Doe", DateOfBirth = new DateTime(1990, 5, 15) };
// Perform the mapping
var viewModel = SimpleMapper.Map<User, UserViewModel>(user);
Console.WriteLine($"Mapped FirstName: {viewModel.FirstName}"); // "John"
Console.WriteLine($"Id: {viewModel.Id}"); // 1
Console.WriteLine($"FullName: {viewModel.FullName}"); // Empty, as there's no matching source property
}
}
Explanation:
-
Reflection: The core logic uses
Type.GetProperties()
to inspect the public, instance properties of the source and destination types at runtime. - Property Matching: It iterates through the destination properties and looks for a source property with the exact same name and type. This is a simple but effective matching strategy.
-
Performance and Caching: Reflection is notoriously slow. Calling
GetProperties()
repeatedly for the same type pair would be a major performance bottleneck. The_mapperCache
is critical. It stores a compiledAction<object, object>
delegate for each type pair. The first timeMap<User, UserViewModel>
is called, it performs the reflection, builds the delegate, and stores it. Subsequent calls for the same types will retrieve the cached delegate instantly from theConcurrentDictionary
, completely avoiding reflection. -
Generics and Constraints: The method is generic (
<TSource, TDestination>
), making it reusable. Thewhere TDestination : new()
constraint is important because it ensures that we can create an instance of the destination type withnew TDestination()
. -
Delegate Compilation: The code creates a list of small
Action
delegates, one for each property to be copied. It then combines them into a single delegate. This is more efficient than looping throughPropertyInfo
objects and callingGetValue
/SetValue
on every map operation. The compiled delegate is what gets cached.
create .Net coding exercises for experienced developer with solutions
Of course. Here are several .NET coding exercises for experienced developers. These exercises are designed to test knowledge of architectural patterns, advanced framework features, performance, and concurrency within the .NET ecosystem.
Goal: Design an application where new functionality can be added by simply dropping a new assembly (DLL) into a folder, without recompiling the main application.
-
Problem: You are building a data processing pipeline. The main application defines an
IProcessor
interface. You need to be able to load and execute allIProcessor
implementations found in DLLs within a specific "plugins" directory. This should be done at runtime. -
Key Concepts: Reflection, Assembly Loading, Dependency Injection (DI) with convention-based registration, or Managed Extensibility Framework (MEF) concepts.
Solution (using .NET's built-in DI and Reflection):
1. Define the Contract (in a shared Class Library project):
- Create a new Class Library project named
Plugin.Contracts
. - This library will be referenced by both the main application and all plugins.
IProcessor.cs
in Plugin.Contracts
namespace Plugin.Contracts;
public interface IProcessor
{
string ProcessorName { get; }
Task<string> ProcessAsync(string input);
}
2. Create a Plugin (in a separate Class Library project):
- Create a new Class Library project named
TextPlugins
. - Reference the
Plugin.Contracts
project.
ToUpperProcessor.cs
in TextPlugins
using Plugin.Contracts;
namespace TextPlugins;
public class ToUpperProcessor : IProcessor
{
public string ProcessorName => "To Upper Case";
public Task<string> ProcessAsync(string input)
{
return Task.FromResult(input.ToUpper());
}
}
ReverseProcessor.cs
in TextPlugins
using Plugin.Contracts;
using System.Linq;
namespace TextPlugins;
public class ReverseProcessor : IProcessor
{
public string ProcessorName => "Reverse String";
public Task<string> ProcessAsync(string input)
{
return Task.FromResult(new string(input.Reverse().ToArray()));
}
}
3. Create the Host Application (Console App):
- Create a new Console App project named
PluginHostApp
. - Reference the
Plugin.Contracts
project. - Add the
Microsoft.Extensions.DependencyInjection
package:dotnet add package Microsoft.Extensions.DependencyInjection
Program.cs
in PluginHostApp
using Microsoft.Extensions.DependencyInjection;
using Plugin.Contracts;
using System.Reflection;
public class Program
{
public static async Task Main(string[] args)
{
var services = new ServiceCollection();
// Register plugins
LoadAndRegisterPlugins(services);
var serviceProvider = services.BuildServiceProvider();
// --- Usage ---
// Get all registered IProcessor instances
var processors = serviceProvider.GetServices<IProcessor>();
Console.WriteLine($"{processors.Count()} processors loaded.");
Console.WriteLine("--------------------------");
const string sampleInput = "Hello, World!";
foreach (var processor in processors)
{
Console.WriteLine($"--- Running Processor: {processor.ProcessorName} ---");
Console.WriteLine($"Input: {sampleInput}");
string result = await processor.ProcessAsync(sampleInput);
Console.WriteLine($"Output: {result}\n");
}
}
private static void LoadAndRegisterPlugins(IServiceCollection services)
{
// Define the path to the plugins directory
string pluginPath = Path.Combine(AppContext.BaseDirectory, "plugins");
if (!Directory.Exists(pluginPath))
{
Console.WriteLine($"Plugin directory not found: {pluginPath}");
// Create for demonstration
Directory.CreateDirectory(pluginPath);
Console.WriteLine("Created plugin directory. Copy your plugin DLLs there.");
return;
}
Console.WriteLine($"Loading plugins from: {pluginPath}");
// Load assemblies from the plugin directory
foreach (var file in Directory.GetFiles(pluginPath, "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(file);
// Find all types in the assembly that implement IProcessor
var processorTypes = assembly.GetTypes()
.Where(t => typeof(IProcessor).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
foreach (var type in processorTypes)
{
// Register each found type with the DI container
// We register it as a transient service that implements IProcessor
services.AddTransient(typeof(IProcessor), type);
Console.WriteLine($" -> Registered plugin: {type.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading plugin from {file}: {ex.Message}");
}
}
}
}
To Run:
- Build all three projects.
- In the
PluginHostApp
's output directory (e.g.,bin/Debug/net8.0
), create a folder namedplugins
. - Copy the
TextPlugins.dll
file into thisplugins
folder. - Run
PluginHostApp.exe
. It will dynamically find, load, register, and execute the processors from the DLL.
Explanation:
-
Shared Contract: The
IProcessor
interface inPlugin.Contracts
is the crucial link. It ensures type safety and a common understanding between the host and the plugins. -
Dynamic Assembly Loading:
Assembly.LoadFrom(file)
loads a DLL into the application's context at runtime. -
Reflection:
assembly.GetTypes()
and the subsequent LINQ query inspect the loaded assembly's contents to find any class that implements theIProcessor
interface. -
Convention-based DI Registration: Instead of explicitly writing
services.AddTransient<IProcessor, ToUpperProcessor>()
, we do it programmatically. This allows the host to remain completely ignorant of the concrete plugin types. TheGetServices<IProcessor>()
call then resolves all registered implementations.
Goal: Build a robust API client that can handle transient network faults using policies for retries and circuit breakers.
-
Problem: You are integrating with an external API that is occasionally unreliable. Simple
try-catch
blocks are not enough; you need to automatically retry failed requests and stop calling the service for a short period if it fails repeatedly (a circuit breaker). -
Key Concepts:
IHttpClientFactory
,Polly
policies (Retry
,CircuitBreaker
), ASP.NET Core DI.
Solution (in an ASP.NET Core Web API project):
1. Install Polly Package:
dotnet add package Microsoft.Extensions.Http.Polly
2. Define a Typed HttpClient
and its Interface:
public interface IUnreliableApiClient
{
Task<string> GetDataAsync();
}
public class UnreliableApiClient : IUnreliableApiClient
{
private readonly HttpClient _httpClient;
public UnreliableApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
// The base address is often configured during DI setup
_httpClient.BaseAddress = new Uri("http://httpstat.us/"); // A service for testing HTTP statuses
}
public async Task<string> GetDataAsync()
{
// We'll intentionally call an endpoint that sometimes fails
var response = await _httpClient.GetStringAsync("503"); // 503 Service Unavailable
return response;
}
}
3. Configure Polly Policies in Program.cs
:
using Polly;
using Polly.Extensions.Http;
var builder = WebApplication.CreateBuilder(args);
// --- Polly Policies ---
// 1. Retry Policy: Retry 3 times with an exponential backoff
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError() // Handles HttpRequestException, 5xx, and 408 statuses
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) // Also retry on 404
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 2s, 4s, 8s
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Console.WriteLine($"[Retry] Retrying in {timespan.TotalSeconds}s... Attempt {retryAttempt}. " +
$"Reason: {outcome.Result?.StatusCode}");
});
// 2. Circuit Breaker Policy: Break after 5 consecutive failures
var circuitBreakerPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30),
onBreak: (result, timespan) => Console.WriteLine($"[CircuitBreaker] Breaking for {timespan.TotalSeconds}s... Reason: {result.Result?.StatusCode}"),
onReset: () => Console.WriteLine("[CircuitBreaker] Resetting."),
onHalfOpen: () => Console.WriteLine("[CircuitBreaker] Half-open: next call is a trial."));
// --- DI Configuration ---
builder.Services.AddHttpClient<IUnreliableApiClient, UnreliableApiClient>()
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy); // Policies are executed in order
// ... Add controllers, etc.
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
4. Create a Controller to Use the Client:
[ApiController]
[Route("[controller]")]
public class DemoController : ControllerBase
{
private readonly IUnreliableApiClient _apiClient;
public DemoController(IUnreliableApiClient apiClient)
{
_apiClient = apiClient;
}
[HttpGet("test-resilience")]
public async Task<IActionResult> TestResilience()
{
try
{
var data = await _apiClient.GetDataAsync();
return Ok(data);
}
catch (Exception ex)
{
// This will catch Polly's BrokenCircuitException or the final HttpRequestException
return StatusCode(500, ex.Message);
}
}
}
Explanation:
-
Polly Policies: Polly is a library for defining resilience policies. We define two:
-
Retry:
WaitAndRetryAsync
handles transient failures. The exponential backoff (Math.Pow(2, retryAttempt)
) is a best practice to avoid overwhelming the failing service. -
Circuit Breaker:
CircuitBreakerAsync
is a state machine. After 5 consecutive failures (handledEventsAllowedBeforeBreaking
), it "breaks" and will immediately fail any subsequent calls for 30 seconds (durationOfBreak
), without even making a network request. This protects our application from a service that is completely down. After 30s, it enters a "Half-Open" state, allowing one trial call to see if the service has recovered.
-
Retry:
-
IHttpClientFactory
Integration: The magic happens withAddPolicyHandler()
. This extension method wraps theHttpClient
's message handler with our Polly policies. When_apiClient.GetDataAsync()
is called, the request pipeline looks like this:Retry Policy -> Circuit Breaker Policy -> HttpClient Handler
. -
Typed Clients: Defining
IUnreliableApiClient
and its implementation is a best practice. It makes the code cleaner, more testable (you can mock the interface), and allowsIHttpClientFactory
to manage theHttpClient
's lifetime correctly.
Goal: Understand and implement low-allocation, high-throughput I/O processing for a custom network protocol.
-
Problem: You need to write a TCP server that parses a simple length-prefixed message protocol. A message consists of a 4-byte integer (the length of the message body) followed by the message body itself (UTF-8 string). Using traditional
byte[]
buffers and streams can lead to high memory allocation and pressure on the Garbage Collector. UseSystem.IO.Pipelines
to create a more efficient server. -
Key Concepts:
System.IO.Pipelines
,Pipe
,PipeReader
,PipeWriter
,ReadOnlySequence<byte>
,Span<T>
.
Solution:
1. The TCP Server using Pipelines
(Program.cs
)
using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Program
{
public static async Task Main(string[] args)
{
var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 8087));
listenSocket.Listen(120);
Console.WriteLine("Server listening on port 8087...");
while (true)
{
var socket = await listenSocket.AcceptAsync();
_ = ProcessLinesAsync(socket); // Fire-and-forget processing for each client
}
}
private static async Task ProcessLinesAsync(Socket socket)
{
Console.WriteLine($"[+] Client connected: {socket.RemoteEndPoint}");
var pipe = new Pipe();
// Start two concurrent tasks: one filling the pipe, one reading from it.
Task writing = FillPipeAsync(socket, pipe.Writer);
Task reading = ReadPipeAsync(pipe.Reader);
await Task.WhenAll(reading, writing);
Console.WriteLine($"[-] Client disconnected: {socket.RemoteEndPoint}");
}
private static async Task FillPipeAsync(Socket socket, PipeWriter writer)
{
const int minimumBufferSize = 512;
while (true)
{
try
{
// Request a memory block from the PipeWriter
Memory<byte> memory = writer.GetMemory(minimumBufferSize);
int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
if (bytesRead == 0) break; // Connection closed
// Tell the PipeWriter how much data was actually written
writer.Advance(bytesRead);
// Make the data available to the PipeReader and check for backpressure
FlushResult result = await writer.FlushAsync();
if (result.IsCompleted) break;
}
catch (Exception ex)
{
Console.WriteLine($"Error filling pipe: {ex.Message}");
break;
}
}
await writer.CompleteAsync();
}
private static async Task ReadPipeAsync(PipeReader reader)
{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
// Process all complete messages in the buffer
while (TryReadMessage(ref buffer, out string? message))
{
Console.WriteLine($" -> Received Message: {message}");
}
// Tell the pipe how much of the buffer we have consumed
reader.AdvanceTo(buffer.Start, buffer.End);
if (result.IsCompleted) break;
}
await reader.CompleteAsync();
}
// This is the core protocol parsing logic
private static bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out string? message)
{
message = null;
if (buffer.Length < 4) return false; // Not enough data for the length prefix
// Read the 4-byte length prefix without slicing or copying if possible
var lengthSpan = buffer.Slice(0, 4);
int messageLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lengthSpan));
// Check if the full message body is in the buffer yet
if (buffer.Length < 4 + messageLength) return false;
// Slice the complete message from the buffer
var messageBuffer = buffer.Slice(4, messageLength);
message = Encoding.UTF8.GetString(messageBuffer);
// Advance the buffer past the message we just processed
buffer = buffer.Slice(messageBuffer.End);
return true;
}
}
2. A Simple TCP Client to Test the Server (Client.cs
in another project)
using System.Net;
using System.Net.Sockets;
using System.Text;
var client = new Socket(SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(IPAddress.Loopback, 8087);
Console.WriteLine("Connected to server.");
await SendMessage("Hello");
await Task.Delay(500);
await SendMessage("This is a longer message from the client.");
await Task.Delay(500);
await SendMessage("Goodbye");
client.Shutdown(SocketShutdown.Both);
async Task SendMessage(string text)
{
byte[] body = Encoding.UTF8.GetBytes(text);
// Convert length to network byte order (big-endian)
byte[] lengthPrefix = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(body.Length));
await client.SendAsync(lengthPrefix, SocketFlags.None);
await client.SendAsync(body, SocketFlags.None);
Console.WriteLine($"Sent: {text}");
}
Explanation:
-
Decoupling with a
Pipe
: ThePipe
object acts as a high-performance, in-memory buffer that decouples the network I/O (FillPipeAsync
) from the protocol parsing (ReadPipeAsync
). This is a key pattern. One task can be reading from the network while another is simultaneously parsing data that has already been received. -
Memory Management: Instead of
new byte[size]
, we usewriter.GetMemory()
. ThePipe
manages a pool of memory blocks, significantly reducing GC pressure. We "rent" a buffer and return it when we're done. -
Backpressure:
writer.FlushAsync()
returns aFlushResult
. If the pipe's internal buffer is full (because the reader is slow), this call will asynchronously wait. This built-in backpressure mechanism prevents an application from being overwhelmed by a fast sender. -
ReadOnlySequence<byte>
: ThePipeReader
gives us aReadOnlySequence<byte>
, which represents a sequence of memory blocks. This is crucial because a single logical message from the network might span multiple underlying memory buffers. OurTryReadMessage
logic can handle this composite buffer without needing to copy it into a single contiguous array. -
Zero-Copy Slicing:
buffer.Slice()
is a very cheap operation that creates a view over the existing memory without allocating or copying new data. This is fundamental to the performance of pipelines.
what is prev in React ?
Of course! This is an excellent and fundamental question in React.
In React, prev
is a conventional name for a variable that holds the previous state when you are updating a component's state. It is used in the "functional update" form of a state setter function (like the one returned by the useState
hook).
Let's break it down.
When you declare state with useState
, you get back an array with two elements: the current state value and a function to update it.
const [count, setCount] = useState(0);
You can call setCount
in two ways:
-
Passing a new value directly:
setCount(5); // Sets the count to 5 setCount(count + 1); // Sets the count to the current value of 'count' + 1
-
Passing a function (the "functional update" form): This is where
prev
comes in.setCount(prevCount => prevCount + 1);
-
prevCount
(you can name it anything, butprev
orprevState
is the convention) is a parameter provided by React itself. - React guarantees that the value of this parameter will be the most up-to-date state value at the moment the update is applied.
- The function should return the new state value.
-
You might wonder, "Why not just use setCount(count + 1)
all the time?"
The reason is that React state updates can be asynchronous and batched. React may group multiple setState
calls into a single re-render for performance. This can lead to a problem called "stale state."
A Classic Example: The "Wrong" Way
Imagine you have a button that should increment a counter three times on a single click. A beginner might write this:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleTripleIncrement = () => {
// ❌ This will NOT work as expected
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleTripleIncrement}>Increment by 3</button>
</div>
);
}
If you click this button when the count is 0
, the count will only become 1
, not 3
.
Why it fails:
The handleTripleIncrement
function "closes over" the value of count
from the render it was created in. When you click the button, count
is 0
. All three setCount(count + 1)
calls are essentially setCount(0 + 1)
. React batches these updates and sees three requests to set the state to 1
. The final result is just 1
.
The "Correct" Way with prev
Now, let's fix it using the functional update form:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleTripleIncrement = () => {
// ✅ This works perfectly!
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleTripleIncrement}>Increment by 3</button>
</div>
);
}
Why it works:
When you pass a function to setCount
, React queues these functions. When it processes the queue, it executes them in order, ensuring the return value of one update becomes the prevCount
for the next one.
- The first call receives
0
asprevCount
and returns1
. - The second call receives the new state
1
asprevCount
and returns2
. - The third call receives the new state
2
asprevCount
and returns3
.
The final result is 3
, as expected.
If your new state depends on the previous state, always use the functional update form.
-
Toggling a boolean:
// Good: Always works, regardless of batching. setIsVisible(prevIsVisible => !prevIsVisible); // Bad: Can be unreliable if multiple toggles are called in the same event. setIsVisible(!isVisible);
-
Adding an item to an array:
// Good: Guarantees you are adding to the most recent version of the array. setItems(prevItems => [...prevItems, newItem]); // Bad: `items` might be stale if another update was queued. setItems([...items, newItem]);
In summary, prev
is your tool to safely and reliably update state when the new state is calculated from the old state, protecting you from issues caused by React's asynchronous and batched nature.
create coding exercises in Python with solutions
Of course! Here is a set of Python coding exercises with solutions, structured by difficulty level. Each exercise includes the problem, the core concepts it covers, a complete solution, and a detailed explanation.
These exercises cover the fundamentals: variables, control flow, functions, and basic data structures.
- Goal: Understand string manipulation and conditional logic.
-
Task: Create a function that takes a string and returns
True
if the string is a palindrome andFalse
if it is not. A palindrome is a word that reads the same forwards and backwards (e.g., "racecar", "madam"). The check should be case-insensitive and should ignore spaces and punctuation. -
Concepts to Use: Functions, String methods (
.lower()
,.isalnum()
), String Slicing.
Solution:
import string
def is_palindrome(s):
"""
Checks if a string is a palindrome, ignoring case, spaces, and punctuation.
"""
# 1. Clean the string: convert to lowercase and remove non-alphanumeric characters.
# A list comprehension is used here for a concise way to filter characters.
cleaned_s = "".join(char.lower() for char in s if char.isalnum())
# 2. Check if the cleaned string is equal to its reverse.
# s[::-1] is a Python slice that reverses a string.
return cleaned_s == cleaned_s[::-1]
# --- Example Usage ---
print(f"'Racecar' is a palindrome: {is_palindrome('Racecar')}")
print(f"'hello' is a palindrome: {is_palindrome('hello')}")
print(f"'A man, a plan, a canal: Panama' is a palindrome: {is_palindrome('A man, a plan, a canal: Panama')}")
# Expected Output:
# 'Racecar' is a palindrome: True
# 'hello' is a palindrome: False
# 'A man, a plan, a canal: Panama' is a palindrome: True
Explanation:
-
Cleaning the String: The most important part is to prepare the string for comparison. We create a new string
cleaned_s
that contains only the letters and numbers from the original string, all in lowercase.-
char.lower()
: Converts each character to lowercase. -
char.isalnum()
: ReturnsTrue
only if the character is a letter (a-z
) or a number (0-9
). -
"".join(...)
: Joins the characters that pass theisalnum()
filter back into a single string.
-
-
Reversing and Comparing: Python's string slicing makes reversing incredibly easy.
cleaned_s[::-1]
creates a reversed copy of the string. The function then simply returns the result of comparing the cleaned string with its reversed version.
- Goal: Understand loops and conditional statements with lists.
-
Task: Create a function that takes a list of numbers and returns the largest number in the list. Do not use the built-in
max()
function. -
Concepts to Use: Functions,
for
loops,if
statements.
Solution:
def find_largest(numbers):
"""
Finds the largest number in a list of numbers without using the max() function.
"""
# 1. Handle the edge case of an empty list.
if not numbers:
return None # Or raise an error, e.g., raise ValueError("Input list cannot be empty")
# 2. Initialize the largest number found so far to be the first element.
largest_so_far = numbers[0]
# 3. Loop through the rest of the numbers in the list.
for number in numbers[1:]:
# 4. If the current number is greater, update our variable.
if number > largest_so_far:
largest_so_far = number
return largest_so_far
# --- Example Usage ---
my_list = [5, 2, 9, 1, 7, 4]
empty_list = []
print(f"The largest number in {my_list} is: {find_largest(my_list)}")
print(f"Result for an empty list: {find_largest(empty_list)}")
# Expected Output:
# The largest number in [5, 2, 9, 1, 7, 4] is: 9
# Result for an empty list: None
Explanation:
-
Edge Case: It's good practice to first check if the list is empty (
if not numbers:
). If it is, we can't find a largest number, so we returnNone
. -
Initialization: We assume the first number in the list is the largest one to start with and store it in
largest_so_far
. -
Iteration: We then loop through every other number in the list (starting from the second element using
numbers[1:]
). -
Comparison: Inside the loop, we compare the
number
we are currently looking at with ourlargest_so_far
. If the current number is bigger, we've found a new "largest," so we update the value oflargest_so_far
. After the loop has finished,largest_so_far
will hold the overall largest value.
These exercises introduce more complex data structures, file I/O, and basic object-oriented programming.
- Goal: Work with files, dictionaries, and string manipulation.
- Task: Write a function that reads a text file and returns a dictionary where the keys are the words in the file and the values are the number of times each word appeared. The word count should be case-insensitive.
-
Concepts to Use: Dictionaries, File I/O (
with open(...)
), String methods (.split()
).
Setup: First, create a text file named sample.txt
in the same directory as your Python script with the following content:
Hello world
This is a sample file
Hello again world
Solution:
import string
def count_words(filepath):
"""
Counts the frequency of each word in a text file.
"""
word_counts = {}
try:
# 1. Use 'with' to automatically handle closing the file.
with open(filepath, 'r') as file:
for line in file:
# 2. Clean each line: remove punctuation and convert to lowercase.
# str.maketrans creates a translation table to remove punctuation.
translator = str.maketrans('', '', string.punctuation)
cleaned_line = line.translate(translator).lower()
# 3. Split the line into words.
words = cleaned_line.split()
# 4. Count each word.
for word in words:
# Use .get(word, 0) to handle the first time a word is seen.
word_counts[word] = word_counts.get(word, 0) + 1
except FileNotFoundError:
return "Error: The file was not found."
return word_counts
# --- Example Usage ---
word_frequencies = count_words('sample.txt')
print(word_frequencies)
# Expected Output:
# {'hello': 2, 'world': 2, 'this': 1, 'is': 1, 'a': 1, 'sample': 1, 'file': 1, 'again': 1}
Explanation:
-
File Handling: The
with open(filepath, 'r') as file:
syntax is the standard way to open files in Python. It ensures the file is automatically closed even if errors occur. -
Cleaning: Before splitting, each line is cleaned.
string.punctuation
provides a string of common punctuation marks.str.maketrans
andline.translate()
are an efficient way to remove all these characters at once. We also convert the line to lowercase. -
Splitting:
line.split()
splits a string into a list of words, using whitespace as the delimiter by default. -
Counting with a Dictionary: We iterate through the
words
. For eachword
, we update its count in theword_counts
dictionary. The lineword_counts[word] = word_counts.get(word, 0) + 1
is a very common and useful pattern:-
word_counts.get(word, 0)
tries to get the current count of theword
. If theword
is not yet in the dictionary (i.e., we're seeing it for the first time), it returns the default value of0
. - We then add
1
to this value and update the dictionary.
-
- Goal: Understand basic Object-Oriented Programming (OOP) by creating classes and objects.
-
Task: Create a
Contact
class and aContactBook
class.-
Contact
: Should have attributes forname
andphone_number
. -
ContactBook
: Should manage a list ofContact
objects. It needs methods toadd_contact
,remove_contact
, andfind_contact
by name.
-
-
Concepts to Use: Classes, Objects, Methods,
__init__
, Instance attributes.
Solution:
class Contact:
"""A single contact with a name and phone number."""
def __init__(self, name, phone_number):
self.name = name
self.phone_number = phone_number
def __str__(self):
# Provides a user-friendly string representation of the object.
return f"Contact: {self.name}, Phone: {self.phone_number}"
class ContactBook:
"""Manages a collection of Contact objects."""
def __init__(self):
# The list of contacts is initialized as an empty list.
self.contacts = []
def add_contact(self, contact):
"""Adds a Contact object to the book."""
self.contacts.append(contact)
print(f"Added contact: {contact.name}")
def remove_contact(self, name):
"""Removes a contact by name."""
contact_to_remove = self.find_contact(name)
if contact_to_remove:
self.contacts.remove(contact_to_remove)
print(f"Removed contact: {name}")
else:
print(f"Contact not found: {name}")
def find_contact(self, name):
"""Finds and returns a contact by name."""
for contact in self.contacts:
if contact.name.lower() == name.lower():
return contact
return None
def display_all(self):
"""Displays all contacts in the book."""
if not self.contacts:
print("Contact book is empty.")
else:
print("--- All Contacts ---")
for contact in self.contacts:
print(contact)
# --- Example Usage ---
book = ContactBook()
# Create contact objects
contact1 = Contact("Alice", "123-456-7890")
contact2 = Contact("Bob", "987-654-3210")
# Use the ContactBook methods
book.add_contact(contact1)
book.add_contact(contact2)
book.display_all()
print("\nFinding Alice...")
found = book.find_contact("Alice")
if found:
print(f"Found: {found}")
print("\nRemoving Bob...")
book.remove_contact("Bob")
book.display_all()
Explanation:
-
Contact
Class: This is a simple data class. The__init__
method is the constructor, which runs when you create a new object (e.g.,Contact("Alice", ...)
).self
refers to the instance of the object being created. -
ContactBook
Class: This class holds the business logic. Its__init__
method creates an empty listself.contacts
to store theContact
objects. -
Methods:
-
add_contact
simply appends a newContact
object to theself.contacts
list. -
find_contact
iterates through the list and returns the entireContact
object if the name matches. -
remove_contact
first usesfind_contact
to locate the object and then uses the list's built-in.remove()
method to delete it.
-
These exercises cover more complex topics like decorators, generators, and concurrency.
- Goal: Understand decorators and memoization for performance optimization.
-
Task: Create a decorator called
@cache_result
that caches the return value of a function. When the function is called with the same arguments again, the decorator should return the cached value instead of re-executing the function. This is a technique called memoization. -
Concepts to Use: Decorators, Closures, Dictionaries for caching,
*args
and**kwargs
.
Solution:
import time
import functools
def cache_result(func):
"""A decorator to cache the results of a function call."""
cache = {} # The cache is stored in a closure.
@functools.wraps(func) # Preserves the original function's metadata
def wrapper(*args, **kwargs):
# Create a key for the cache based on the function's arguments.
# We convert kwargs to a frozenset of items to make it hashable.
key = (args, frozenset(kwargs.items()))
if key not in cache:
print(f"Cache miss for key: {key}. Executing function...")
# If the result is not in the cache, call the original function.
cache[key] = func(*args, **kwargs)
else:
print(f"Cache hit for key: {key}. Returning cached result.")
return cache[key]
return wrapper
@cache_result
def slow_fetch_data(url, params=None):
"""A slow function that simulates a network request."""
print(f"Fetching data from {url} with params {params}...")
time.sleep(2) # Simulate a 2-second delay
return f"Data from {url}"
# --- Example Usage ---
print("First call:")
data1 = slow_fetch_data("https://api.example.com/data")
print(f"Received: {data1}\n")
print("Second call (should be fast):")
data2 = slow_fetch_data("https://api.example.com/data")
print(f"Received: {data2}\n")
print("Third call with different args (should be slow):")
data3 = slow_fetch_data("https://api.example.com/other_data")
print(f"Received: {data3}\n")
Explanation:
-
Decorator Structure: A decorator is a function (
cache_result
) that takes another function (func
) as an argument and returns a new function (wrapper
). -
Closure: The
wrapper
function has access to thecache
dictionary even after thecache_result
function has finished executing. This is a closure, and it's what allows the cache to persist between function calls. -
@functools.wraps(func)
: This is a helper decorator that copies the metadata (like the function name__name__
and docstring__doc__
) from the original function (func
) to thewrapper
function. It's a best practice to use it. -
Handling Arguments: The
wrapper
accepts*args
(for positional arguments) and**kwargs
(for keyword arguments) so it can decorate any function, regardless of its signature. -
Cache Key: To store results, we need a unique key for each combination of arguments. A tuple
(args, frozenset(kwargs.items()))
is used because tuples and frozensets are hashable, meaning they can be used as dictionary keys. -
Logic: Inside the
wrapper
, we first check if thekey
is in ourcache
. If it is, we immediately return the cached value. If not, we execute the originalfunc
, store its result in the cache, and then return it.
create Python coding exercises for experienced developer with solutions
Of course. Here is a set of Python coding exercises designed for experienced developers. These problems focus on advanced language features, design patterns, concurrency, and performance optimization.
Goal: Master asynchronous programming with asyncio
, control concurrency, and handle API rate limits gracefully.
-
Problem: You need to scrape data from a list of URLs from an API that has a rate limit (e.g., no more than 10 requests per second). The scraping should be done concurrently to be efficient, but without violating the rate limit. Your scraper should be able to handle potential HTTP errors for individual requests without crashing the entire process.
-
Key Concepts:
asyncio
,aiohttp
for asynchronous HTTP requests,asyncio.Semaphore
for concurrency control,asyncio.gather
for running tasks concurrently.
Solution:
Setup: You'll need to install the aiohttp
library.
pip install aiohttp
async_scraper.py
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
"""Asynchronously fetches a single URL and returns its text content."""
try:
async with session.get(url, timeout=10) as response:
# Raise an exception for bad status codes (4xx or 5xx)
response.raise_for_status()
print(f"Successfully fetched {url}")
return await response.text()
except asyncio.TimeoutError:
print(f"Timeout error when fetching {url}")
except aiohttp.ClientError as e:
print(f"Client error for {url}: {e}")
return None
async def rate_limited_scraper(urls, max_concurrent_requests, requests_per_second):
"""
Scrapes a list of URLs with rate limiting and concurrency control.
"""
# Semaphore limits the number of concurrent requests.
semaphore = asyncio.Semaphore(max_concurrent_requests)
# Calculate the delay needed to respect the rate limit.
delay_between_requests = 1.0 / requests_per_second
tasks = []
results = {}
async with aiohttp.ClientSession() as session:
for url in urls:
# Wait for the semaphore to allow a new request.
await semaphore.acquire()
# Create the task to fetch the URL.
task = asyncio.create_task(fetch_url(session, url))
# Add a callback to the task to release the semaphore when it's done.
task.add_done_callback(lambda t: semaphore.release())
tasks.append((url, task))
# Wait for the calculated delay before dispatching the next request.
await asyncio.sleep(delay_between_requests)
# Wait for all dispatched tasks to complete and gather results.
for url, task in tasks:
content = await task
if content:
results[url] = len(content)
return results
async def main():
# Use a mock API for demonstration
urls_to_scrape = [f"https://httpbin.org/get?id={i}" for i in range(20)]
start_time = time.time()
# Configuration: 5 concurrent workers, but only 10 requests per second overall.
scraped_data = await rate_limited_scraper(urls_to_scrape,
max_concurrent_requests=5,
requests_per_second=10)
end_time = time.time()
print("\n--- Scraping Complete ---")
print(f"Processed {len(scraped_data)} URLs in {end_time - start_time:.2f} seconds.")
# for url, length in scraped_data.items():
# print(f"{url}: Content length = {length}")
if __name__ == "__main__":
asyncio.run(main())
Explanation:
-
aiohttp.ClientSession
: This is the standard way to make multiple HTTP requests withaiohttp
. It manages connection pooling, making it much more efficient than creating a new connection for each request. -
asyncio.Semaphore
: The semaphore is initialized withmax_concurrent_requests
. The lineawait semaphore.acquire()
acts as a gatekeeper. If the semaphore's internal counter is greater than zero, it decrements the counter and proceeds. If the counter is zero, it waits until another coroutine callssemaphore.release()
. This effectively limits how manyfetch_url
coroutines can be "in-flight" at any given time. -
Rate Limiting with
asyncio.sleep
: The lineawait asyncio.sleep(delay_between_requests)
is crucial for rate limiting. It ensures that we don't start new tasks faster than therequests_per_second
limit allows, regardless of how many workers are available in the semaphore. -
Decoupled Release: The
task.add_done_callback(lambda t: semaphore.release())
is an elegant way to release the semaphore. It ensures that the slot is freed up as soon as a task finishes (whether successfully or with an error), allowing another waiting task to proceed. This is more efficient than waiting for a batch of tasks to finish before releasing their semaphore slots. -
Error Handling: The
try...except
block withinfetch_url
handles errors on a per-request basis. This resilience means that one failed URL won't stop the entire scraping process.
Goal: Deepen understanding of context managers (with
statements) and error handling by creating a reusable utility for database transactions.
-
Problem: When working with databases (like SQLite, PostgreSQL), you often need to perform a series of operations within a single transaction. If any operation fails, the entire transaction should be rolled back. If all succeed, it should be committed. A context manager is the perfect Pythonic way to handle this setup and teardown logic.
-
Key Concepts: Context Managers (
__enter__
,__exit__
), Database APIs (e.g.,sqlite3
), Exception Handling.
Solution:
import sqlite3
import os
class DatabaseTransaction:
"""
A context manager to handle SQLite database transactions automatically.
- Commits on successful exit of the 'with' block.
- Rolls back on any exception within the 'with' block.
"""
def __init__(self, db_path):
self.db_path = db_path
self.connection = None
self.cursor = None
def __enter__(self):
"""Called when entering the 'with' block."""
self.connection = sqlite3.connect(self.db_path)
# Setting isolation_level to None enables autocommit mode,
# but we will manually control transactions with BEGIN/COMMIT/ROLLBACK.
self.connection.isolation_level = None
self.cursor = self.connection.cursor()
print("-> Entering context: Beginning transaction...")
self.cursor.execute("BEGIN TRANSACTION")
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting the 'with' block."""
if exc_type is None:
# No exception occurred, so commit the transaction.
print("<- Exiting context: Committing transaction...")
self.connection.commit()
else:
# An exception occurred, so roll back the transaction.
print(f"<- Exiting context: Exception occurred. Rolling back transaction...")
self.connection.rollback()
self.connection.close()
# Returning False (or None) will re-raise the exception if one occurred.
# Returning True would suppress the exception.
return False
# --- Example Usage ---
DB_FILE = "inventory.db"
# Clean up previous runs
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
# Setup initial database state
with sqlite3.connect(DB_FILE) as conn:
conn.execute("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, stock INTEGER)")
conn.execute("INSERT INTO products (name, stock) VALUES ('Laptop', 10)")
conn.execute("INSERT INTO products (name, stock) VALUES ('Mouse', 50)")
def process_order(product_name, quantity):
"""Processes an order within a transaction."""
print(f"\nProcessing order for {quantity} of {product_name}...")
try:
with DatabaseTransaction(DB_FILE) as cursor:
# 1. Check current stock
cursor.execute("SELECT stock FROM products WHERE name = ?", (product_name,))
stock = cursor.fetchone()[0]
if stock < quantity:
# This will raise an exception, triggering the rollback in __exit__
raise ValueError("Insufficient stock!")
# 2. Update stock
new_stock = stock - quantity
cursor.execute("UPDATE products SET stock = ? WHERE name = ?", (new_stock, product_name))
print("Order processed successfully inside 'with' block.")
except ValueError as e:
print(f"Order failed: {e}")
# --- Test Cases ---
# 1. Successful transaction
process_order('Laptop', 3)
# 2. Failed transaction (should rollback)
process_order('Mouse', 100)
# 3. Verify final state of the database
with sqlite3.connect(DB_FILE) as conn:
print("\n--- Final Database State ---")
for row in conn.execute("SELECT * FROM products"):
print(row)
# Expected Output: Laptop stock should be 7, Mouse stock should remain 50.
Explanation:
-
__enter__
Method: This method is executed when thewith
statement is entered. It is responsible for setting up the context. Here, it establishes the database connection, gets a cursor, and explicitly starts a transaction withBEGIN TRANSACTION
. It must return the object that will be assigned to the variable afteras
(in this case, thecursor
). -
__exit__
Method: This method is executed when the block is exited, either normally or due to an exception. It receives three arguments:exc_type
,exc_val
, andexc_tb
.- If
exc_type
isNone
, it means the block completed without any errors, so we callself.connection.commit()
. - If
exc_type
is notNone
, an exception occurred. We callself.connection.rollback()
to undo all changes made within the transaction. - The method then cleans up by closing the connection.
- If
-
Resource Management and Atomicity: This pattern provides perfect encapsulation. The code inside the
with
block doesn't need to know about committing or rolling back; it just performs its database operations. The context manager guarantees that the transaction is handled correctly and that the database connection is always closed, making the code much cleaner and less error-prone.
Goal: Explore Python's powerful metaprogramming capabilities by creating a metaclass that automatically registers classes into a central registry.
-
Problem: You are designing a system (e.g., a web framework, a game engine) that uses plugins. You want any class that inherits from a specific
BasePlugin
to be automatically registered in a global dictionary without requiring any explicit registration call or decorator. -
Key Concepts: Metaclasses,
type()
,__new__
method of a metaclass, Class creation process.
Solution:
# The registry where our plugins will be stored.
PLUGIN_REGISTRY = {}
class PluginMeta(type):
"""
A metaclass that automatically registers all subclasses of BasePlugin.
"""
def __new__(cls, name, bases, attrs):
# 1. Create the new class object using the 'type' metaclass's __new__ method.
new_class = super().__new__(cls, name, bases, attrs)
# 2. Perform the registration logic.
# We don't want to register the base class itself, only its subclasses.
if name != "BasePlugin":
plugin_name = attrs.get('plugin_name')
if not plugin_name:
raise TypeError(f"Plugin {name} must define a 'plugin_name' class attribute.")
if plugin_name in PLUGIN_REGISTRY:
raise ValueError(f"Duplicate plugin name found: {plugin_name}")
print(f"--> Registering plugin '{plugin_name}' for class {name}")
PLUGIN_REGISTRY[plugin_name] = new_class
return new_class
class BasePlugin(metaclass=PluginMeta):
"""The base class for all plugins. It uses our custom metaclass."""
plugin_name = None # Subclasses must override this.
def execute(self):
raise NotImplementedError
# --- Define some plugins. The registration happens automatically at class definition time. ---
class DataExportPlugin(BasePlugin):
plugin_name = "export_csv"
def execute(self):
print("Executing CSV data export...")
class ImageProcessingPlugin(BasePlugin):
plugin_name = "resize_image"
def execute(self):
print("Executing image resizing...")
class InvalidPlugin(BasePlugin):
# This will be registered, but has no specific logic.
plugin_name = "invalid"
# --- Example Usage: A function that uses the registry ---
def run_plugin(name):
"""Finds a plugin in the registry and runs it."""
print(f"\nAttempting to run plugin: '{name}'")
plugin_class = PLUGIN_REGISTRY.get(name)
if not plugin_class:
print("Plugin not found.")
return
# Create an instance of the found plugin class and execute it.
plugin_instance = plugin_class()
plugin_instance.execute()
# Run the plugins using the registry
run_plugin("export_csv")
run_plugin("resize_image")
run_plugin("non_existent_plugin")
# You can inspect the registry directly
print("\n--- Current Plugin Registry ---")
print(PLUGIN_REGISTRY)
Explanation:
-
What is a Metaclass? In Python, classes are themselves objects. A metaclass is the "class of a class." It defines how a class is created. The default metaclass is
type
. -
PluginMeta
: We define our own metaclass. The__new__
method of a metaclass is the key. It is called before the class object is actually created and returned. It receives the name of the class being created (name
), its base classes (bases
), and its attributes/methods (attrs
). -
The Logic in
__new__
:-
super().__new__(...)
: We first call the parent metaclass's (type
's)__new__
method to let it do the actual work of creating the class object. -
Registration: After the class object (
new_class
) is created, we can inspect it and perform our custom logic. We check that it's not theBasePlugin
itself, get its requiredplugin_name
attribute from theattrs
dictionary, and add the class object to our globalPLUGIN_REGISTRY
.
-
-
Connecting the Metaclass: The line
class BasePlugin(metaclass=PluginMeta):
tells Python to use ourPluginMeta
to construct theBasePlugin
class and any class that inherits from it. -
Automatic Registration: When the Python interpreter reads the definitions for
DataExportPlugin
andImageProcessingPlugin
, it sees that their parent usesPluginMeta
. It therefore callsPluginMeta.__new__
to create them, which triggers our registration logic. This happens automatically, without needing decorators or any explicit function calls.
create code examples of CRUD operations for SUPABASE in React
Of course! Here is a comprehensive guide with code examples for performing CRUD (Create, Read, Update, Delete) operations in a React application using Supabase.
This guide will walk you through:
- Project Setup: Setting up Supabase and a new React project.
- Connecting to Supabase: Creating a client to communicate with your database.
- CRUD Operations: Building a simple "Todos" application to demonstrate each operation.
- Bonus: Implementing real-time updates with Supabase Subscriptions.
-
Create a Supabase Project:
- Go to supabase.com and sign in.
- Click "New project" and give it a name and a strong database password.
- Wait for your new project to be provisioned.
-
Create a
todos
Table:- In your project's dashboard, go to the SQL Editor in the left sidebar.
- Click "+ New query".
- Paste and run the following SQL to create a
todos
table:
create table todos ( id bigint generated by default as identity primary key, created_at timestamp with time zone default now(), task text not null, is_complete boolean default false ); -- For this simple example, we'll disable Row Level Security (RLS). -- In a real-world app, you should learn about and enable RLS. alter table todos enable row level security; create policy "Public users can view all todos" on todos for select using (true); create policy "Users can insert their own todos" on todos for insert with check (true); create policy "Users can update their own todos" on todos for update using (true); create policy "Users can delete their own todos" on todos for delete using (true);
This updated script now includes basic RLS policies that allow any public user to perform CRUD operations, which is fine for a simple demo. In a real app, you would restrict these policies based on authenticated user IDs (
auth.uid()
). -
Get API Keys:
- Go to Project Settings (the gear icon).
- Click on the API tab.
- You will need your Project URL and the
anon
public key. Keep this page open.
-
Create a new React App (using Vite for speed):
npm create vite@latest my-supabase-app -- --template react cd my-supabase-app
-
Install the Supabase client library:
npm install @supabase/supabase-js
-
Set up Environment Variables:
- Create a new file named
.env
in the root of your project. - Add your Supabase URL and anon key to this file. Important: Vite requires environment variables to be prefixed with
VITE_
.
VITE_SUPABASE_URL=YOUR_SUPABASE_PROJECT_URL VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
- Create a new file named
It's best practice to create a single, shared Supabase client instance for your entire application.
- Create a new file:
src/supabaseClient.js
- Add the following code to initialize the client:
// src/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Now you can import the supabase
object anywhere in your app to interact with your database.
Let's create a single component to handle all CRUD operations for our todos.
Create a new file: src/Todos.jsx
// src/Todos.jsx
import { useState, useEffect } from 'react';
import { supabase } from './supabaseClient';
function Todos() {
const [todos, setTodos] = useState([]);
const [newTask, setNewTask] = useState('');
const [editingId, setEditingId] = useState(null);
const [editingText, setEditingText] = useState('');
useEffect(() => {
// This function will be defined below
getTodos();
}, []);
// READ: Fetch all todos from the database
const getTodos = async () => {
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: true });
if (error) {
console.error('Error fetching todos:', error);
} else {
setTodos(data);
}
};
// CREATE: Add a new todo to the database
const addTodo = async (e) => {
e.preventDefault();
if (!newTask.trim()) return;
const { data, error } = await supabase
.from('todos')
.insert({ task: newTask })
.select() // This returns the new row
.single();
if (error) {
console.error('Error adding todo:', error);
} else {
setTodos([...todos, data]);
setNewTask('');
}
};
// UPDATE: Toggle the is_complete status of a todo
const toggleTodo = async (id, currentStatus) => {
const { error } = await supabase
.from('todos')
.update({ is_complete: !currentStatus })
.eq('id', id);
if (error) {
console.error('Error toggling todo:', error);
} else {
// Update the state locally for a faster UI response
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, is_complete: !currentStatus } : todo
)
);
}
};
// UPDATE: Save the edited task text
const saveTodo = async (id) => {
const { error } = await supabase
.from('todos')
.update({ task: editingText })
.eq('id', id);
if (error) {
console.error('Error updating todo:', error);
} else {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, task: editingText } : todo
)
);
setEditingId(null);
setEditingText('');
}
};
// DELETE: Remove a todo from the database
const deleteTodo = async (id) => {
const { error } = await supabase
.from('todos')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting todo:', error);
} else {
// Update the state locally
setTodos(todos.filter((todo) => todo.id !== id));
}
};
return (
<div className="todo-container">
<h1>Supabase Todos</h1>
{/* Create Form */}
<form onSubmit={addTodo}>
<input
type="text"
placeholder="Add a new task..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button type="submit">Add</button>
</form>
{/* Read/Render List */}
<ul>
{todos.map((todo) => (
<li key={todo.id} className={todo.is_complete ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.is_complete}
onChange={() => toggleTodo(todo.id, todo.is_complete)}
/>
{editingId === todo.id ? (
<input
type="text"
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && saveTodo(todo.id)}
/>
) : (
<span onDoubleClick={() => {
setEditingId(todo.id);
setEditingText(todo.task);
}}>
{todo.task}
</span>
)}
{editingId === todo.id ? (
<>
<button onClick={() => saveTodo(todo.id)}>Save</button>
<button onClick={() => setEditingId(null)}>Cancel</button>
</>
) : (
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
)}
</li>
))}
</ul>
</div>
);
}
export default Todos;
- Modify
src/App.jsx
to use your new component:
// src/App.jsx
import Todos from './Todos';
import './App.css'; // We will create this file next
function App() {
return (
<div className="App">
<Todos />
</div>
);
}
export default App;
- Create a CSS file
src/App.css
for basic styling:
/* src/App.css */
body {
font-family: sans-serif;
background-color: #f4f4f4;
color: #333;
display: flex;
justify-content: center;
padding-top: 50px;
}
.todo-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 500px;
}
h1 {
text-align: center;
color: #444;
}
form {
display: flex;
margin-bottom: 1rem;
}
form input[type="text"] {
flex-grow: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
form button {
padding: 0.5rem 1rem;
border: none;
background-color: #28a745;
color: white;
border-radius: 4px;
margin-left: 0.5rem;
cursor: pointer;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: flex;
align-items: center;
padding: 0.8rem 0;
border-bottom: 1px solid #eee;
}
li.completed span {
text-decoration: line-through;
color: #aaa;
}
li input[type="checkbox"] {
margin-right: 1rem;
}
li span {
flex-grow: 1;
cursor: pointer;
}
li button {
border: none;
padding: 0.4rem 0.8rem;
border-radius: 4px;
cursor: pointer;
margin-left: 0.5rem;
}
li button:last-of-type {
background-color: #dc3545;
color: white;
}
You're all set! Run your React app:
npm run dev
Open your browser to http://localhost:5173
(or whatever URL Vite provides). You should now have a fully functional CRUD application!
One of Supabase's best features is real-time updates. Let's modify Todos.jsx
to listen for database changes automatically.
- Modify the
useEffect
hook insrc/Todos.jsx
:
// src/Todos.jsx (Updated useEffect)
useEffect(() => {
// Initial fetch
getTodos();
// Set up the subscription
const channel = supabase
.channel('todos-channel')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'todos' },
(payload) => {
console.log('Change received!', payload);
// We can use the payload to update the state
// This is a simple approach: just re-fetch the data
getTodos();
}
)
.subscribe();
// Cleanup function to unsubscribe
return () => {
supabase.removeChannel(channel);
};
}, []);
What's happening here?
- We still call
getTodos()
once on initial load. -
supabase.channel(...)
creates a new subscription channel. -
.on(...)
listens for specific events. We're listening for all (*
) changes in thepublic
schema on thetodos
table. - The callback function receives a
payload
with information about the change. The simplest way to update the UI is to just callgetTodos()
again to fetch the fresh list. -
Crucially, the
useEffect
hook returns a cleanup function. When the component unmounts, it unsubscribes from the channel to prevent memory leaks.
Now, if you open your app in two browser windows, adding, updating, or deleting a todo in one window will cause the other window to update automatically
Neon authentication for React projects
Of course! Here is a comprehensive guide on how to implement authentication in a React project using a Neon serverless Postgres database.
It's crucial to understand that React (the frontend) cannot and should not connect directly to your Neon database. This would expose your database credentials to anyone viewing your website's source code, which is a massive security risk.
Instead, we will build a simple backend server (using Node.js and Express) that acts as a secure intermediary:
- React Frontend: Manages the user interface (login forms, registration forms, protected content). It sends user credentials to the backend.
- Node.js/Express Backend: Receives requests from the React app. It securely connects to the Neon database, validates user credentials, hashes passwords, and creates authentication tokens (JWTs).
- Neon (Postgres) Database: Stores the user data, such as usernames and hashed passwords.
Here's the data flow for a login attempt:
[React App] ---> [API Request with email/password] ---> [Node.js/Express Server]
^ |
| v
[Updates UI] <--- [Response with JWT Token or Error] <--- [Queries Neon DB]
Before you start, make sure you have the following installed:
- Node.js and npm
- A code editor like VS Code
- An account on Neon
-
Create a Neon Project:
- Log in to your Neon account and create a new project.
- Once the project is ready, navigate to the Dashboard.
-
Get the Connection String:
- On your project dashboard, find the Connection Details widget.
- Select the Node.js driver option.
- Copy the connection string. It will look something like
postgres://user:[email protected]/dbname?sslmode=require
. We'll use this in our backend.
-
Create a
users
Table:- In the Neon console, go to the SQL Editor.
- Run the following SQL query to create a table to store your users. We'll use
uuid
for a unique user ID and store hashed passwords.
-- Enable the uuid-ossp extension to generate UUIDs CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Create the users table CREATE TABLE users ( user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP );
Your database is now ready to store user data.
This server will handle registration and login logic.
-
Initialize Project:
mkdir neon-auth-backend cd neon-auth-backend npm init -y
-
Install Dependencies:
-
express
: The web server framework. -
pg
: The Node.js client for PostgreSQL (to talk to Neon). -
bcryptjs
: To securely hash and compare passwords. -
jsonwebtoken
: To create and verify JSON Web Tokens (JWTs). -
cors
: To allow our React app to make requests to this server. -
dotenv
: To manage environment variables (like our database connection string).
npm install express pg bcryptjs jsonwebtoken cors dotenv
-
-
Set Up Environment Variables: Create a file named
.env
in theneon-auth-backend
root. This file should never be committed to Git.# Paste your Neon connection string here DATABASE_URL="postgres://user:password@..." # A secret key for signing JWTs. Make this a long, random string. JWT_SECRET="YOUR_SUPER_SECRET_RANDOM_STRING"
-
Create the Server (
server.js
): Create a file namedserver.js
and add the following code. Comments explain each part.// server.js require('dotenv').config(); const express = require('express'); const cors = require('cors'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { Pool } = require('pg'); const app = express(); // --- MIDDLEWARE --- app.use(cors()); // Allows cross-origin requests (from our React frontend) app.use(express.json()); // Allows us to parse JSON in request bodies // --- DATABASE CONNECTION --- const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false, // Required for Neon }, }); // --- API ROUTES --- // 1. REGISTRATION ROUTE app.post('/api/auth/register', async (req, res) => { const { email, password } = req.body; // Basic validation if (!email || !password) { return res.status(400).json({ error: 'Email and password are required.' }); } try { // Hash the password const salt = await bcrypt.genSalt(10); const passwordHash = await bcrypt.hash(password, salt); // Store new user in the database const newUser = await pool.query( 'INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING user_id, email', [email, passwordHash] ); res.status(201).json({ message: 'User registered successfully!', user: newUser.rows[0], }); } catch (error) { console.error('Registration Error:', error); // Check for unique constraint violation (email already exists) if (error.code === '23505') { return res.status(409).json({ error: 'Email already in use.' }); } res.status(500).json({ error: 'Internal server error' }); } }); // 2. LOGIN ROUTE app.post('/api/auth/login', async (req, res) => { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: 'Email and password are required.' }); } try { // Find the user by email const userResult = await pool.query('SELECT * FROM users WHERE email = $1', [email]); const user = userResult.rows[0]; if (!user) { return res.status(401).json({ error: 'Invalid credentials.' }); // User not found } // Compare submitted password with stored hash const isMatch = await bcrypt.compare(password, user.password_hash); if (!isMatch) { return res.status(401).json({ error: 'Invalid credentials.' }); // Passwords don't match } // Create a JWT const payload = { user: { id: user.user_id, email: user.email, }, }; const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h', // Token expires in 1 hour }); res.json({ message: 'Logged in successfully!', token, user: { id: user.user_id, email: user.email }, }); } catch (error) { console.error('Login Error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // --- START SERVER --- const PORT = process.env.PORT || 5001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
-
Run the Backend Server:
node server.js
Your backend is now running and listening for requests on port 5001.
-
Create React App: Open a new terminal window and run:
npx create-react-app neon-auth-frontend cd neon-auth-frontend
-
Install Dependencies:
-
react-router-dom
: For handling routing (e.g., navigating between login, register, and dashboard pages). -
axios
: A popular library for making HTTP requests to our backend.
npm install react-router-dom axios
-
-
Create Components: Inside the
src
folder, create the following files:src/components/Login.js
import React, { useState } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); setError(''); try { const response = await axios.post('http://localhost:5001/api/auth/login', { email, password, }); // Store the token (e.g., in localStorage) localStorage.setItem('token', response.data.token); // Redirect to a protected route navigate('/dashboard'); } catch (err) { setError(err.response?.data?.error || 'Failed to login'); } }; return ( <div> <h2>Login</h2> <form onSubmit={handleSubmit}> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit">Login</button> </form> {error && <p style={{ color: 'red' }}>{error}</p>} </div> ); }; export default Login;
src/components/Register.js
import React, { useState } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; const Register = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [message, setMessage] = useState(''); const [error, setError] = useState(''); const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); setMessage(''); setError(''); try { await axios.post('http://localhost:5001/api/auth/register', { email, password, }); setMessage('Registration successful! Please login.'); setTimeout(() => navigate('/login'), 2000); // Redirect to login after 2 seconds } catch (err) { setError(err.response?.data?.error || 'Failed to register'); } }; return ( <div> <h2>Register</h2> <form onSubmit={handleSubmit}> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit">Register</button> </form> {message && <p style={{ color: 'green' }}>{message}</p>} {error && <p style={{ color: 'red' }}>{error}</p>} </div> ); }; export default Register;
src/components/Dashboard.js
(A protected page)import React from 'react'; import { useNavigate } from 'react-router-dom'; const Dashboard = () => { const navigate = useNavigate(); const handleLogout = () => { localStorage.removeItem('token'); navigate('/login'); }; return ( <div> <h2>Dashboard</h2> <p>Welcome! You are logged in.</p> <button onClick={handleLogout}>Logout</button> </div> ); }; export default Dashboard;
src/components/ProtectedRoute.js
(To protect routes)import React from 'react'; import { Navigate } from 'react-router-dom'; const ProtectedRoute = ({ children }) => { const token = localStorage.getItem('token'); if (!token) { // If no token, redirect to the login page return <Navigate to="/login" />; } // If token exists, render the child components return children; }; export default ProtectedRoute;
-
Set Up Routing in
App.js
: Modifysrc/App.js
to set up the routes for your application.// src/App.js import React from 'react'; import { BrowserRouter as Router, Routes, Route, Link, Navigate, } from 'react-router-dom'; import Login from './components/Login'; import Register from './components/Register'; import Dashboard from './components/Dashboard'; import ProtectedRoute from './components/ProtectedRoute'; import './App.css'; function App() { return ( <Router> <div className="App"> <nav> <ul> <li> <Link to="/login">Login</Link> </li> <li> <Link to="/register">Register</Link> </li> </ul> </nav> <main> <Routes> <Route path="/login" element={<Login />} /> <Route path="/register" element={<Register />} /> <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } /> {/* Redirect root to login page */} <Route path="/" element={<Navigate to="/login" />} /> </Routes> </main> </div> </Router> ); } export default App;
-
Run the React App: Make sure your backend server is still running in its terminal. In the frontend terminal, run:
npm start
Your React app will open in the browser at
http://localhost:3000
. You can now navigate between the Login and Register pages, create an account (which gets stored in Neon!), log in, and access the protected dashboard.