frontend-developer
SKILL.md
Frontend Developer Expert
You are a frontend development expert specializing in modern JavaScript frameworks, responsive design, and user experience optimization.
Frontend Frameworks Comparison
React
// Modern React with Hooks
import React, { useState, useEffect, useContext, useCallback } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';
// Custom Hook for data fetching
const useUserProfile = (userId) => {
const queryClient = new QueryClient();
return useQuery(
['user', userId],
async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
{
enabled: !!userId,
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
}
);
};
// Context for global state
const ThemeContext = React.createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// Component using hooks
const UserProfile = ({ userId }) => {
const { data: user, isLoading, error, refetch } = useUserProfile(userId);
const { theme } = useContext(ThemeContext);
const navigate = useNavigate();
useEffect(() => {
document.title = user ? `${user.name} - Profile` : 'Loading...';
}, [user]);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div className={`profile ${theme}`}>
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={() => navigate('/edit')}>Edit Profile</button>
<button onClick={() => refetch()}>Refresh</button>
</div>
);
};
// App component
const App = () => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<BrowserRouter>
<Routes>
<Route path="/users/:id" element={<UserProfile />} />
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
</QueryClientProvider>
);
};
Vue 3 (Composition API)
// Vue 3 with Composition API
import { ref, computed, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import axios from 'axios';
export default {
name: 'UserProfile',
props: {
userId: {
type: String,
required: true
}
},
setup(props) {
const router = useRouter();
const store = useStore();
// Reactive state
const user = ref(null);
const loading = ref(false);
const error = ref(null);
// Computed properties
const displayName = computed(() => {
return user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Guest';
});
const isAdmin = computed(() => {
return user.value?.role === 'admin';
});
// Methods
const fetchUser = async () => {
loading.value = true;
error.value = null;
try {
const response = await axios.get(`/api/users/${props.userId}`);
user.value = response.data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
const updateUser = async (updates) => {
try {
const response = await axios.put(`/api/users/${props.userId}`, updates);
user.value = response.data;
store.dispatch('showNotification', {
type: 'success',
message: 'Profile updated successfully'
});
} catch (err) {
store.dispatch('showNotification', {
type: 'error',
message: 'Failed to update profile'
});
}
};
// Lifecycle hooks
onMounted(() => {
fetchUser();
});
// Watchers
watch(() => props.userId, (newId, oldId) => {
if (newId !== oldId) {
fetchUser();
}
});
return {
user,
loading,
error,
displayName,
isAdmin,
fetchUser,
updateUser
};
}
};
// Template
/*
<template>
<div class="user-profile">
<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else-if="user">
<h1>{{ displayName }}</h1>
<p>{{ user.email }}</p>
<button v-if="isAdmin" @click="editProfile">Edit</button>
<button @click="fetchUser">Refresh</button>
</div>
</div>
</template>
*/
Angular
// Angular service
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError, BehaviorSubject } from 'rxjs';
import { catchError, tap, shareReplay } from 'rxjs/operators';
import { User } from './user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
// State management with BehaviorSubject
private currentUser$ = new BehaviorSubject<User | null>(null);
public currentUser = this.currentUser$.asObservable();
constructor(private http: HttpClient) {}
getUsers(filters?: any): Observable<User[]> {
let params = new HttpParams();
if (filters) {
if (filters.page) params = params.append('page', filters.page.toString());
if (filters.limit) params = params.append('limit', filters.limit.toString());
if (filters.search) params = params.append('search', filters.search);
}
return this.http.get<User[]>(this.apiUrl, { params })
.pipe(
tap(users => console.log(`Fetched ${users.length} users`)),
catchError(this.handleError)
);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`)
.pipe(
tap(user => this.currentUser$.next(user)),
shareReplay(1), // Cache the result
catchError(this.handleError)
);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user)
.pipe(
tap(user => this.currentUser$.next(user)),
catchError(this.handleError)
);
}
updateUser(user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${user.id}`, user)
.pipe(
tap(updatedUser => this.currentUser$.next(updatedUser)),
catchError(this.handleError)
);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`)
.pipe(
tap(() => this.currentUser$.next(null)),
catchError(this.handleError)
);
}
private handleError(error: any) {
console.error('An error occurred:', error);
return throwError(() => error);
}
}
// Angular component
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-user-profile',
template: `
<div class="user-profile" *ngIf="user">
<h1>{{ user.firstName }} {{ user.lastName }}</h1>
<p>{{ user.email }}</p>
<button (click)="editProfile()">Edit Profile</button>
<button (click)="deleteUser()">Delete</button>
</div>
`,
styles: [`
.user-profile {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
`]
})
export class UserProfileComponent implements OnInit, OnDestroy {
user: User | null = null;
private destroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {}
ngOnInit(): void {
const userId = this.route.snapshot.paramMap.get('id');
this.userService.getUser(Number(userId))
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (user) => {
this.user = user;
},
error: (error) => {
console.error('Failed to load user:', error);
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
editProfile(): void {
this.router.navigate(['/users', this.user?.id, 'edit']);
}
deleteUser(): void {
if (this.user && confirm('Are you sure you want to delete this user?')) {
this.userService.deleteUser(this.user.id)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: () => {
this.router.navigate(['/users']);
},
error: (error) => {
console.error('Failed to delete user:', error);
}
});
}
}
}
State Management
Redux Toolkit (React)
import { createSlice, configureStore } from '@reduxjs/toolkit';
// User slice
const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
loading: false,
error: null
},
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
clearUser: (state) => {
state.user = null;
}
}
});
export const { setUser, setLoading, setError, clearUser } = userSlice.actions;
// Async thunks
export const fetchUser = (userId) => async (dispatch) => {
dispatch(setLoading(true));
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch(setUser(data));
dispatch(setError(null));
} catch (error) {
dispatch(setError(error.message));
} finally {
dispatch(setLoading(false));
}
};
// Store configuration
const store = configureStore({
reducer: {
user: userSlice.reducer,
// other reducers...
}
});
export default store;
Pinia (Vue)
// User store
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null);
const loading = ref(false);
const error = ref(null);
// Getters
const isAuthenticated = computed(() => !!user.value);
const isAdmin = computed(() => user.value?.role === 'admin');
// Actions
async function fetchUser(userId) {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
user.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
async function updateUser(updates) {
loading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${user.value.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
const data = await response.json();
user.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
function clearUser() {
user.value = null;
error.value = null;
}
return {
// State
user,
loading,
error,
// Getters
isAuthenticated,
isAdmin,
// Actions
fetchUser,
updateUser,
clearUser
};
});
NgRx (Angular)
// Actions
import { createAction, props } from '@ngrx/store';
export const loadUser = createAction(
'[User] Load User',
props<{ userId: number }>()
);
export const loadUserSuccess = createAction(
'[User] Load User Success',
props<{ user: User }>()
);
export const loadUserFailure = createAction(
'[User] Load User Failure',
props<{ error: string }>()
);
// Reducer
import { createReducer, on } from '@ngrx/store';
export interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}
export const initialState: UserState = {
user: null,
loading: false,
error: null
};
export const userReducer = createReducer(
initialState,
on(loadUser, (state) => ({
...state,
loading: true,
error: null
})),
on(loadUserSuccess, (state, { user }) => ({
...state,
user,
loading: false
})),
on(loadUserFailure, (state, { error }) => ({
...state,
error,
loading: false
}))
);
// Effects
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { UserService } from './user.service';
import * as UserActions from './user.actions';
@Injectable()
export class UserEffects {
loadUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUser),
mergeMap((action) =>
this.userService.getUser(action.userId).pipe(
map((user) => UserActions.loadUserSuccess({ user })),
catchError((error) =>
of(UserActions.loadUserFailure({ error: error.message }))
)
)
)
)
);
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
Styling & CSS
CSS-in-JS (Styled Components)
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: ${props => props.primary ? '#0056b3' : '#545b62'};
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
const Card = styled.div`
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 10px;
@media (max-width: 768px) {
padding: 15px;
margin: 5px;
}
`;
// Usage
const UserProfile = ({ user }) => (
<Card>
<h1>{user.name}</h1>
<p>{user.email}</p>
<Button primary>Edit Profile</Button>
</Card>
);
Tailwind CSS
<!-- Utility-first CSS framework -->
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
<div class="md:flex">
<div class="md:shrink-0">
<img class="h-48 w-full object-cover md:h-full md:w-48"
src="/img/store.jpg"
alt="Store">
</div>
<div class="p-8">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
Case study
</div>
<a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">
Integrating with Tailwind CSS
</a>
<p class="mt-2 text-slate-500">
Getting a new project off the ground is always a challenge. We've made it easier with pre-built components and examples.
</p>
</div>
</div>
</div>
<!-- Responsive design with Tailwind -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="bg-white p-6 rounded-lg shadow">Item 1</div>
<div class="bg-white p-6 rounded-lg shadow">Item 2</div>
<div class="bg-white p-6 rounded-lg shadow">Item 3</div>
</div>
<!-- Dark mode -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
<h1 class="text-2xl font-bold">Dark Mode Support</h1>
</div>
CSS Modules
/* UserProfile.module.css */
.profileContainer {
display: flex;
align-items: center;
gap: 20px;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
}
.userInfo {
flex: 1;
}
.userName {
font-size: 24px;
font-weight: bold;
margin-bottom: 8px;
}
.userEmail {
color: #666;
font-size: 14px;
}
.editButton {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.editButton:hover {
background: #0056b3;
}
import styles from './UserProfile.module.css';
const UserProfile = ({ user }) => (
<div className={styles.profileContainer}>
<img src={user.avatar} alt={user.name} className={styles.avatar} />
<div className={styles.userInfo}>
<h1 className={styles.userName}>{user.name}</h1>
<p className={styles.userEmail}>{user.email}</p>
<button className={styles.editButton}>Edit Profile</button>
</div>
</div>
);
Performance Optimization
Code Splitting
// React - Lazy loading components
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Memoization
import { memo, useMemo, useCallback } from 'react';
// Memo component to prevent unnecessary re-renders
const ExpensiveComponent = memo(({ data, onAction }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
value: item.value * 2
}));
}, [data]);
const handleClick = useCallback((id) => {
onAction(id);
}, [onAction]);
return (
<div>
{processedData.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.value}
</div>
))}
</div>
);
});
Virtual Scrolling
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
Row {index}
</div>
);
const VirtualList = ({ items }) => (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
Testing
Unit Testing (Jest + React Testing Library)
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserProfile from './UserProfile';
describe('UserProfile', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
avatar: 'avatar.jpg'
};
test('renders user information', async () => {
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
});
test('handles edit button click', async () => {
const user = userEvent.setup();
render(<UserProfile userId={1} />);
await waitFor(() => {
const editButton = screen.getByText('Edit Profile');
expect(editButton).toBeInTheDocument();
});
await user.click(screen.getByText('Edit Profile'));
// Assert navigation or state change
});
test('shows loading state', () => {
render(<UserProfile userId={1} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
test('handles error state', async () => {
// Mock failed API call
jest.spyOn(global, 'fetch').mockRejectedValueOnce(
new Error('Failed to fetch')
);
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});
Best Practices
Component Design
✅ DO:
- Keep components small and focused
- Use composition over inheritance
- Follow single responsibility principle
- Use functional components with hooks
- Implement proper TypeScript types
- Write reusable components
- Use proper prop validation
- Handle loading and error states
- Make components accessible
- Test components thoroughly
❌ DON'T:
- Create huge monolithic components
- Mix business logic with UI
- Ignore TypeScript errors
- Skip error handling
- Forget about accessibility
- Write tightly coupled code
- Ignore performance
Performance
✅ DO:
- Use code splitting
- Implement lazy loading
- Optimize images
- Use memoization wisely
- Implement virtual scrolling for long lists
- Minimize bundle size
- Use production builds
- Implement caching strategies
- Optimize re-renders
❌ DON'T:
- Bundle everything together
- Load unnecessary dependencies
- Ignore bundle size
- Over-memoize (premature optimization)
- Render large lists without virtualization
- Use large images unoptimized
Tools & Resources
Build Tools
- Vite: Fast build tool
- Webpack: Module bundler
- Rollup: Library bundler
- esbuild: Extremely fast bundler
- Turbopack: Next.js bundler
Development Tools
- ESLint: Linting
- Prettier: Code formatting
- TypeScript: Type safety
- Jest: Testing framework
- Cypress: E2E testing