Context API 🌐
Context API je vestavěné řešení Reactu pro globální stav, které nám umožňuje předávat data přes celý strom komponent bez prop drilling.
🎯 Co je Context API?
Context API poskytuje způsob, jak předávat data přes strom komponent bez nutnosti předávat props na každé úrovni.
Kdy použít Context:
✅ Globální stav - uživatel, téma, jazyk
✅ Konfigurace - API URL, nastavení
✅ Cache - data sdílená mezi komponentami
✅ Authentication - přihlášený uživatel
Kdy NEPOUŽÍVAT Context:
❌ Lokální stav - použijte useState ❌ Časté změny - může způsobit performance problémy ❌ Komplexní logika - použijte Redux nebo Zustand
🚀 Základní použití
1. Vytvoření Contextu:
// contexts/EmployeeContext.jsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import apiService from '../services/api';
// Vytvoření contextu
const EmployeeContext = createContext();
// Provider komponenta
export const EmployeeProvider = ({ children }) => {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [selectedEmployee, setSelectedEmployee] = useState(null);
const [filter, setFilter] = useState('all');
// Načtení dat při mountu
useEffect(() => {
loadEmployees();
}, []);
const loadEmployees = async () => {
try {
setLoading(true);
setError(null);
const data = await apiService.fetchEmployees();
setEmployees(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const addEmployee = async (employeeData) => {
try {
const newEmployee = await apiService.createEmployee(employeeData);
setEmployees(prev => [...prev, newEmployee]);
} catch (err) {
setError(err.message);
}
};
const updateEmployee = async (id, employeeData) => {
try {
const updatedEmployee = await apiService.updateEmployee(id, employeeData);
setEmployees(prev =>
prev.map(emp => emp.id === id ? updatedEmployee : emp)
);
} catch (err) {
setError(err.message);
}
};
const deleteEmployee = async (id) => {
try {
await apiService.deleteEmployee(id);
setEmployees(prev => prev.filter(emp => emp.id !== id));
if (selectedEmployee?.id === id) {
setSelectedEmployee(null);
}
} catch (err) {
setError(err.message);
}
};
const filteredEmployees = filter === 'all'
? employees
: employees.filter(emp => emp.department === filter);
// Hodnota contextu
const value = {
// State
employees: filteredEmployees,
allEmployees: employees,
loading,
error,
selectedEmployee,
filter,
// Actions
setSelectedEmployee,
setFilter,
addEmployee,
updateEmployee,
deleteEmployee,
loadEmployees,
};
return (
<EmployeeContext.Provider value={value}>
{children}
</EmployeeContext.Provider>
);
};
// Custom hook pro použití contextu
export const useEmployee = () => {
const context = useContext(EmployeeContext);
if (!context) {
throw new Error('useEmployee musí být použit uvnitř EmployeeProvider');
}
return context;
};
2. Použití v App komponentě:
// App.jsx
import React from 'react';
import { EmployeeProvider } from './contexts/EmployeeContext';
import EmployeeList from './components/EmployeeList';
import EmployeeDetail from './components/EmployeeDetail';
import LoadingSpinner from './components/LoadingSpinner';
import ErrorMessage from './components/ErrorMessage';
const App = () => {
return (
<EmployeeProvider>
<div className="min-h-screen bg-gray-50">
<div className="max-w-6xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-8">
Seznam zaměstnanců
</h1>
<AppContent />
</div>
</div>
</EmployeeProvider>
);
};
const AppContent = () => {
const { loading, error, loadEmployees } = useEmployee();
if (loading) {
return <LoadingSpinner />;
}
if (error) {
return <ErrorMessage message={error} onRetry={loadEmployees} />;
}
return (
<>
<EmployeeList />
<EmployeeDetail />
</>
);
};
export default App;
3. Použití v komponentách:
// components/EmployeeList.jsx
import React from 'react';
import { useEmployee } from '../contexts/EmployeeContext';
import EmployeeCard from './EmployeeCard';
import FilterBar from './FilterBar';
import AddEmployeeButton from './AddEmployeeButton';
const EmployeeList = () => {
const {
employees,
filter,
setFilter,
setSelectedEmployee
} = useEmployee();
return (
<div className="w-full">
<FilterBar
currentFilter={filter}
onFilterChange={setFilter}
/>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{employees.map(employee => (
<EmployeeCard
key={employee.id}
employee={employee}
onClick={() => setSelectedEmployee(employee)}
/>
))}
</div>
<AddEmployeeButton />
</div>
);
};
export default EmployeeList;
// components/EmployeeDetail.jsx
import React from 'react';
import { useEmployee } from '../contexts/EmployeeContext';
const EmployeeDetail = () => {
const {
selectedEmployee,
setSelectedEmployee,
updateEmployee,
deleteEmployee
} = useEmployee();
if (!selectedEmployee) {
return null;
}
const handleUpdate = async (updatedData) => {
await updateEmployee(selectedEmployee.id, updatedData);
};
const handleDelete = async () => {
await deleteEmployee(selectedEmployee.id);
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<div className="flex justify-between items-start mb-4">
<h2 className="text-2xl font-bold text-gray-900">
{selectedEmployee.name}
</h2>
<button
onClick={() => setSelectedEmployee(null)}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
✕
</button>
</div>
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">Pozice</label>
<p className="text-lg text-gray-900">{selectedEmployee.position}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Oddělení</label>
<p className="text-lg text-gray-900">{selectedEmployee.department}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Email</label>
<p className="text-lg text-blue-600">{selectedEmployee.email}</p>
</div>
</div>
<div className="flex gap-2 mt-6">
<button
onClick={handleUpdate}
className="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded"
>
Upravit
</button>
<button
onClick={handleDelete}
className="flex-1 bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded"
>
Smazat
</button>
</div>
</div>
</div>
);
};
export default EmployeeDetail;
🎨 Pokročilé funkce
1. Více Contextů:
// contexts/ThemeContext.jsx
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value = {
theme,
toggleTheme,
isDark: theme === 'dark'
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme musí být použit uvnitř ThemeProvider');
}
return context;
};
2. Kombinace Contextů:
// App.jsx
import React from 'react';
import { EmployeeProvider } from './contexts/EmployeeContext';
import { ThemeProvider } from './contexts/ThemeContext';
const App = () => {
return (
<ThemeProvider>
<EmployeeProvider>
<AppContent />
</EmployeeProvider>
</ThemeProvider>
);
};
3. Context s TypeScript:
// contexts/EmployeeContext.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
interface Employee {
id: number;
name: string;
position: string;
department: string;
email: string;
phone: string;
}
interface EmployeeContextType {
employees: Employee[];
loading: boolean;
error: string | null;
selectedEmployee: Employee | null;
filter: string;
setSelectedEmployee: (employee: Employee | null) => void;
setFilter: (filter: string) => void;
addEmployee: (employee: Omit<Employee, 'id'>) => Promise<void>;
updateEmployee: (id: number, employee: Partial<Employee>) => Promise<void>;
deleteEmployee: (id: number) => Promise<void>;
}
const EmployeeContext = createContext<EmployeeContextType | undefined>(undefined);
interface EmployeeProviderProps {
children: ReactNode;
}
export const EmployeeProvider: React.FC<EmployeeProviderProps> = ({ children }) => {
// Implementation...
};
export const useEmployee = (): EmployeeContextType => {
const context = useContext(EmployeeContext);
if (!context) {
throw new Error('useEmployee musí být použit uvnitř EmployeeProvider');
}
return context;
};
⚡ Performance optimalizace
1. Rozdělení Contextů:
// Místo jednoho velkého contextu
const AppContext = createContext();
// Rozdělte na menší contexty
const EmployeeContext = createContext();
const ThemeContext = createContext();
const UserContext = createContext();
2. Memoization:
// contexts/EmployeeContext.jsx
import React, { createContext, useContext, useState, useMemo } from 'react';
export const EmployeeProvider = ({ children }) => {
const [employees, setEmployees] = useState([]);
const [selectedEmployee, setSelectedEmployee] = useState(null);
const [filter, setFilter] = useState('all');
// Memoizace hodnoty contextu
const value = useMemo(() => ({
employees,
selectedEmployee,
filter,
setSelectedEmployee,
setFilter,
}), [employees, selectedEmployee, filter]);
return (
<EmployeeContext.Provider value={value}>
{children}
</EmployeeContext.Provider>
);
};
3. Conditional rendering:
// Použijte conditional rendering pro lepší performance
const EmployeeDetail = () => {
const { selectedEmployee } = useEmployee();
// Early return - komponenta se nevyrenderuje, pokud není selectedEmployee
if (!selectedEmployee) {
return null;
}
return (
<div className="employee-detail">
{/* JSX */}
</div>
);
};
📋 Praktické cvičení
- Vytvořte EmployeeContext s všemi potřebnými funkcemi
- Implementujte ThemeContext pro dark/light mode
- Přidejte TypeScript typy pro lepší type safety
- Optimalizujte performance pomocí useMemo
- Otestujte Context v různých komponentách
🚀 Tipy a triky
1. Error boundaries pro Context:
class ContextErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Něco se pokazilo s Context API.</h1>;
}
return this.props.children;
}
}
2. Default hodnoty:
const EmployeeContext = createContext({
employees: [],
loading: false,
error: null,
// Default hodnoty pro lepší debugging
});
Context API je výkonný nástroj pro globální stav. V příští části se podíváme na Reducer pattern pro komplexnější logiku! 🔄