Hook
use to eksperymentalna funkcja React, która pozwala na "używanie" zasobów takich jak Promise czy Context. Jest to pierwszy hook, który może być wywoływany warunkowo i w pętlach, co otwiera nowe możliwości w pisaniu komponentów.1import { use, Suspense } from 'react';
2
3// Funkcja zwracająca Promise
4function fetchUser(id) {
5 return fetch(`/api/users/${id}`).then(res => res.json());
6}
7
8// Komponent używający use z Promise
9function UserProfile({ userPromise }) {
10 // use automatycznie "rozpakowuje" Promise
11 const user = use(userPromise);
12
13 return (
14 <div style={{
15 padding: '20px',
16 border: '1px solid #ddd',
17 borderRadius: '8px'
18 }}>
19 <h2>{user.name}</h2>
20 <p>Email: {user.email}</p>
21 <p>Dołączył: {new Date(user.joinedDate).toLocaleDateString('pl-PL')}</p>
22 </div>
23 );
24}
25
26// Komponent rodzic
27function UserDashboard({ userId }) {
28 // Tworzymy Promise raz, aby uniknąć wielokrotnych requestów
29 const userPromise = fetchUser(userId);
30
31 return (
32 <div>
33 <h1>Panel użytkownika</h1>
34 <Suspense fallback={<div>Ładowanie danych użytkownika...</div>}>
35 <UserProfile userPromise={userPromise} />
36 </Suspense>
37 </div>
38 );
39}1import { use, createContext, useState } from 'react';
2
3// Tworzenie Context
4const ThemeContext = createContext(null);
5const UserContext = createContext(null);
6
7// Komponent używający use z Context
8function Header() {
9 // use może być używany warunkowo!
10 const theme = use(ThemeContext);
11 const user = use(UserContext);
12
13 return (
14 <header style={{
15 backgroundColor: theme === 'dark' ? '#333' : '#f8f9fa',
16 color: theme === 'dark' ? '#fff' : '#333',
17 padding: '20px'
18 }}>
19 <h1>Witaj, {user.name}!</h1>
20 <p>Aktualny motyw: {theme}</p>
21 </header>
22 );
23}
24
25// Możliwość warunkowego użycia
26function ConditionalComponent({ showUserInfo }) {
27 const theme = use(ThemeContext);
28
29 // use może być używany warunkowo - to niemożliwe z innymi hooks!
30 if (showUserInfo) {
31 const user = use(UserContext);
32 return <div>Użytkownik: {user.name}</div>;
33 }
34
35 return <div>Motyw: {theme}</div>;
36}
37
38// App z providers
39function App() {
40 const [theme, setTheme] = useState('light');
41 const [user] = useState({ name: 'Jan Kowalski', id: 1 });
42
43 return (
44 <ThemeContext.Provider value={theme}>
45 <UserContext.Provider value={user}>
46 <div>
47 <Header />
48 <ConditionalComponent showUserInfo={true} />
49 <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
50 Zmień motyw
51 </button>
52 </div>
53 </UserContext.Provider>
54 </ThemeContext.Provider>
55 );
56}1import { use, Suspense } from 'react';
2
3// Funkcja tworząca Promise dla każdego posta
4function fetchPost(id) {
5 return fetch(`/api/posts/${id}`).then(res => res.json());
6}
7
8// Komponent wyświetlający wiele postów
9function PostList({ postIds }) {
10 const posts = postIds.map(id => {
11 // use może być używany w pętlach!
12 const postPromise = fetchPost(id);
13 // eslint-disable-next-line react-hooks/rules-of-hooks
14 const post = use(postPromise);
15 return post;
16 });
17
18 return (
19 <div>
20 {posts.map(post => (
21 <article
22 key={post.id}
23 style={{
24 marginBottom: '20px',
25 padding: '15px',
26 backgroundColor: '#f8f9fa',
27 borderRadius: '8px'
28 }}
29 >
30 <h3>{post.title}</h3>
31 <p>{post.content}</p>
32 <small>Autor: {post.author}</small>
33 </article>
34 ))}
35 </div>
36 );
37}
38
39// Wrapper z Suspense
40function BlogSection() {
41 const postIds = [1, 2, 3, 4, 5];
42
43 return (
44 <section>
45 <h2>Najnowsze posty</h2>
46 <Suspense fallback={<PostListSkeleton count={5} />}>
47 <PostList postIds={postIds} />
48 </Suspense>
49 </section>
50 );
51}
52
53function PostListSkeleton({ count }) {
54 return (
55 <div>
56 {Array.from({ length: count }).map((_, i) => (
57 <div
58 key={i}
59 style={{
60 marginBottom: '20px',
61 padding: '15px',
62 backgroundColor: '#e9ecef',
63 borderRadius: '8px',
64 height: '100px'
65 }}
66 />
67 ))}
68 </div>
69 );
70}1import { use, Suspense, useState } from 'react';
2
3// Prosty cache dla Promise
4const promiseCache = new Map();
5
6function getCachedPromise(key, promiseFactory) {
7 if (!promiseCache.has(key)) {
8 promiseCache.set(key, promiseFactory());
9 }
10 return promiseCache.get(key);
11}
12
13// API functions
14function fetchUserData(userId) {
15 const cached = getCachedPromise(
16 `user-${userId}`,
17 () => fetch(`/api/users/${userId}`).then(res => res.json())
18 );
19 return cached;
20}
21
22function fetchUserPosts(userId) {
23 const cached = getCachedPromise(
24 `posts-${userId}`,
25 () => fetch(`/api/users/${userId}/posts`).then(res => res.json())
26 );
27 return cached;
28}
29
30// Komponenty
31function UserInfo({ userId }) {
32 const userPromise = fetchUserData(userId);
33 const user = use(userPromise);
34
35 return (
36 <div style={{
37 padding: '20px',
38 backgroundColor: '#f8f9fa',
39 borderRadius: '8px',
40 marginBottom: '20px'
41 }}>
42 <h2>{user.name}</h2>
43 <p>📧 {user.email}</p>
44 <p>📍 {user.location}</p>
45 <p>🎂 Dołączył: {new Date(user.joinedDate).toLocaleDateString('pl-PL')}</p>
46 </div>
47 );
48}
49
50function UserPosts({ userId }) {
51 const postsPromise = fetchUserPosts(userId);
52 const posts = use(postsPromise);
53
54 return (
55 <div>
56 <h3>Posty użytkownika ({posts.length})</h3>
57 {posts.map(post => (
58 <article
59 key={post.id}
60 style={{
61 marginBottom: '15px',
62 padding: '15px',
63 border: '1px solid #e9ecef',
64 borderRadius: '6px'
65 }}
66 >
67 <h4>{post.title}</h4>
68 <p>{post.excerpt}</p>
69 <small>📅 {new Date(post.date).toLocaleDateString('pl-PL')}</small>
70 </article>
71 ))}
72 </div>
73 );
74}
75
76function UserProfilePage() {
77 const [userId, setUserId] = useState(1);
78
79 return (
80 <div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
81 <div style={{ marginBottom: '20px' }}>
82 <button onClick={() => setUserId(1)} disabled={userId === 1}>
83 Użytkownik 1
84 </button>
85 <button onClick={() => setUserId(2)} disabled={userId === 2}>
86 Użytkownik 2
87 </button>
88 <button onClick={() => promiseCache.clear()}>
89 Wyczyść cache
90 </button>
91 </div>
92
93 <Suspense fallback={<div>Ładowanie profilu...</div>}>
94 <UserInfo userId={userId} />
95 </Suspense>
96
97 <Suspense fallback={<div>Ładowanie postów...</div>}>
98 <UserPosts userId={userId} />
99 </Suspense>
100 </div>
101 );
102}1import { use, Suspense } from 'react';
2
3// Funkcje API
4async function fetchDashboardData() {
5 const [stats, activities, notifications] = await Promise.all([
6 fetch('/api/stats').then(res => res.json()),
7 fetch('/api/activities').then(res => res.json()),
8 fetch('/api/notifications').then(res => res.json())
9 ]);
10
11 return { stats, activities, notifications };
12}
13
14// Komponent dashboardu
15function Dashboard({ dataPromise }) {
16 const { stats, activities, notifications } = use(dataPromise);
17
18 return (
19 <div style={{
20 display: 'grid',
21 gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
22 gap: '20px',
23 padding: '20px'
24 }}>
25 <StatsCard stats={stats} />
26 <ActivitiesCard activities={activities} />
27 <NotificationsCard notifications={notifications} />
28 </div>
29 );
30}
31
32function StatsCard({ stats }) {
33 return (
34 <div style={{
35 padding: '20px',
36 backgroundColor: '#f8f9fa',
37 borderRadius: '8px',
38 boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
39 }}>
40 <h3>📊 Statystyki</h3>
41 <div style={{ display: 'grid', gap: '10px' }}>
42 <div>
43 <strong>Użytkownicy:</strong> {stats.users.toLocaleString('pl-PL')}
44 </div>
45 <div>
46 <strong>Przychód:</strong> {stats.revenue.toLocaleString('pl-PL', {
47 style: 'currency',
48 currency: 'PLN'
49 })}
50 </div>
51 <div>
52 <strong>Wzrost:</strong>
53 <span style={{ color: stats.growth > 0 ? '#28a745' : '#dc3545' }}>
54 {stats.growth > 0 ? '+' : ''}{stats.growth}%
55 </span>
56 </div>
57 </div>
58 </div>
59 );
60}
61
62function ActivitiesCard({ activities }) {
63 return (
64 <div style={{
65 padding: '20px',
66 backgroundColor: '#f8f9fa',
67 borderRadius: '8px',
68 boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
69 }}>
70 <h3>🎯 Ostatnie aktywności</h3>
71 <ul style={{ listStyle: 'none', padding: 0 }}>
72 {activities.slice(0, 5).map(activity => (
73 <li
74 key={activity.id}
75 style={{
76 padding: '8px 0',
77 borderBottom: '1px solid #e9ecef'
78 }}
79 >
80 <div style={{ fontWeight: '500' }}>{activity.user}</div>
81 <div style={{ fontSize: '14px', color: '#6c757d' }}>
82 {activity.action}
83 </div>
84 <small style={{ color: '#6c757d' }}>
85 {new Date(activity.timestamp).toLocaleString('pl-PL')}
86 </small>
87 </li>
88 ))}
89 </ul>
90 </div>
91 );
92}
93
94function NotificationsCard({ notifications }) {
95 const unreadCount = notifications.filter(n => !n.read).length;
96
97 return (
98 <div style={{
99 padding: '20px',
100 backgroundColor: '#f8f9fa',
101 borderRadius: '8px',
102 boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
103 }}>
104 <h3>
105 🔔 Powiadomienia
106 {unreadCount > 0 && (
107 <span style={{
108 marginLeft: '10px',
109 padding: '2px 8px',
110 backgroundColor: '#dc3545',
111 color: 'white',
112 borderRadius: '12px',
113 fontSize: '14px'
114 }}>
115 {unreadCount}
116 </span>
117 )}
118 </h3>
119 <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
120 {notifications.map(notification => (
121 <div
122 key={notification.id}
123 style={{
124 padding: '10px',
125 marginBottom: '8px',
126 backgroundColor: notification.read ? 'transparent' : '#e3f2fd',
127 borderRadius: '4px',
128 fontSize: '14px'
129 }}
130 >
131 <strong>{notification.title}</strong>
132 <p style={{ margin: '4px 0' }}>{notification.message}</p>
133 </div>
134 ))}
135 </div>
136 </div>
137 );
138}
139
140// Główny komponent
141function DashboardApp() {
142 const dataPromise = fetchDashboardData();
143
144 return (
145 <div>
146 <h1 style={{ padding: '20px' }}>Panel administracyjny</h1>
147 <Suspense fallback={<DashboardSkeleton />}>
148 <Dashboard dataPromise={dataPromise} />
149 </Suspense>
150 </div>
151 );
152}
153
154function DashboardSkeleton() {
155 return (
156 <div style={{
157 display: 'grid',
158 gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
159 gap: '20px',
160 padding: '20px'
161 }}>
162 {[1, 2, 3].map(i => (
163 <div
164 key={i}
165 style={{
166 height: '250px',
167 backgroundColor: '#e9ecef',
168 borderRadius: '8px',
169 animation: 'pulse 1.5s ease-in-out infinite'
170 }}
171 />
172 ))}
173 </div>
174 );
175}1import { use, Suspense, Component } from 'react';
2
3// Error Boundary
4class ErrorBoundary extends Component {
5 constructor(props) {
6 super(props);
7 this.state = { hasError: false, error: null };
8 }
9
10 static getDerivedStateFromError(error) {
11 return { hasError: true, error };
12 }
13
14 componentDidCatch(error, errorInfo) {
15 console.error('Error caught by boundary:', error, errorInfo);
16 }
17
18 render() {
19 if (this.state.hasError) {
20 return (
21 <div style={{
22 padding: '20px',
23 backgroundColor: '#f8d7da',
24 border: '1px solid #f5c6cb',
25 borderRadius: '8px',
26 color: '#721c24'
27 }}>
28 <h3>❌ Coś poszło nie tak</h3>
29 <p>{this.state.error?.message || 'Nieznany błąd'}</p>
30 <button
31 onClick={() => this.setState({ hasError: false, error: null })}
32 style={{
33 marginTop: '10px',
34 padding: '8px 16px',
35 backgroundColor: '#721c24',
36 color: 'white',
37 border: 'none',
38 borderRadius: '4px',
39 cursor: 'pointer'
40 }}
41 >
42 Spróbuj ponownie
43 </button>
44 </div>
45 );
46 }
47
48 return this.props.children;
49 }
50}
51
52// Funkcja API która może zwrócić błąd
53function fetchDataWithError(shouldFail = false) {
54 return new Promise((resolve, reject) => {
55 setTimeout(() => {
56 if (shouldFail) {
57 reject(new Error('Błąd pobierania danych z serwera'));
58 } else {
59 resolve({
60 message: 'Dane załadowane pomyślnie!',
61 timestamp: new Date().toISOString()
62 });
63 }
64 }, 1000);
65 });
66}
67
68// Komponent używający use
69function DataDisplay({ dataPromise }) {
70 const data = use(dataPromise);
71
72 return (
73 <div style={{
74 padding: '20px',
75 backgroundColor: '#d4edda',
76 border: '1px solid #c3e6cb',
77 borderRadius: '8px',
78 color: '#155724'
79 }}>
80 <h3>✅ Sukces!</h3>
81 <p>{data.message}</p>
82 <small>Załadowano: {new Date(data.timestamp).toLocaleString('pl-PL')}</small>
83 </div>
84 );
85}
86
87// Główny komponent
88function ErrorHandlingDemo() {
89 const [shouldFail, setShouldFail] = useState(false);
90 const [key, setKey] = useState(0);
91
92 const dataPromise = fetchDataWithError(shouldFail);
93
94 return (
95 <div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
96 <h2>Obsługa błędów z use</h2>
97
98 <div style={{ marginBottom: '20px' }}>
99 <label>
100 <input
101 type="checkbox"
102 checked={shouldFail}
103 onChange={(e) => setShouldFail(e.target.checked)}
104 />
105 Symuluj błąd
106 </label>
107 <button
108 onClick={() => setKey(k => k + 1)}
109 style={{ marginLeft: '20px' }}
110 >
111 Odśwież
112 </button>
113 </div>
114
115 <ErrorBoundary key={key}>
116 <Suspense fallback={
117 <div style={{
118 padding: '20px',
119 backgroundColor: '#cfe2ff',
120 border: '1px solid #b6d4fe',
121 borderRadius: '8px',
122 color: '#084298'
123 }}>
124 ⏳ Ładowanie danych...
125 </div>
126 }>
127 <DataDisplay dataPromise={dataPromise} />
128 </Suspense>
129 </ErrorBoundary>
130 </div>
131 );
132}1import { use, Suspense, lazy, useState } from 'react';
2
3// Symulacja systemu routingu
4const routes = {
5 '/': lazy(() => import('./pages/Home')),
6 '/about': lazy(() => import('./pages/About')),
7 '/contact': lazy(() => import('./pages/Contact')),
8 '/profile': lazy(() => import('./pages/Profile'))
9};
10
11// Context dla routingu
12const RouteContext = createContext(null);
13
14// Hook do nawigacji
15function useNavigation() {
16 const { navigate } = use(RouteContext);
17 return navigate;
18}
19
20// Komponent strony
21function Page({ path }) {
22 const Component = routes[path] || routes['/'];
23
24 return (
25 <Suspense fallback={<PageLoader />}>
26 <Component />
27 </Suspense>
28 );
29}
30
31function PageLoader() {
32 return (
33 <div style={{
34 display: 'flex',
35 justifyContent: 'center',
36 alignItems: 'center',
37 minHeight: '400px'
38 }}>
39 <div style={{ textAlign: 'center' }}>
40 <div style={{
41 width: '50px',
42 height: '50px',
43 border: '3px solid #f3f3f3',
44 borderTop: '3px solid #007bff',
45 borderRadius: '50%',
46 animation: 'spin 1s linear infinite',
47 margin: '0 auto 20px'
48 }} />
49 <p>Ładowanie strony...</p>
50 </div>
51 </div>
52 );
53}
54
55// Główna aplikacja
56function RouterApp() {
57 const [currentPath, setCurrentPath] = useState('/');
58
59 const navigate = (path) => {
60 setCurrentPath(path);
61 window.history.pushState({}, '', path);
62 };
63
64 return (
65 <RouteContext.Provider value={{ navigate, currentPath }}>
66 <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
67 <Navigation />
68 <main style={{ flex: 1, padding: '20px' }}>
69 <Page path={currentPath} />
70 </main>
71 </div>
72 </RouteContext.Provider>
73 );
74}
75
76function Navigation() {
77 const navigate = useNavigation();
78 const { currentPath } = use(RouteContext);
79
80 const links = [
81 { path: '/', label: 'Strona główna' },
82 { path: '/about', label: 'O nas' },
83 { path: '/contact', label: 'Kontakt' },
84 { path: '/profile', label: 'Profil' }
85 ];
86
87 return (
88 <nav style={{
89 backgroundColor: '#333',
90 padding: '10px 20px'
91 }}>
92 {links.map(link => (
93 <button
94 key={link.path}
95 onClick={() => navigate(link.path)}
96 style={{
97 marginRight: '10px',
98 padding: '8px 16px',
99 backgroundColor: currentPath === link.path ? '#007bff' : 'transparent',
100 color: 'white',
101 border: 'none',
102 borderRadius: '4px',
103 cursor: 'pointer'
104 }}
105 >
106 {link.label}
107 </button>
108 ))}
109 </nav>
110 );
111}1import { use, createContext, useState } from 'react';
2
3// Context dla formularza
4const FormContext = createContext(null);
5
6// Hook do zarządzania formularzem
7function useFormField(name) {
8 const form = use(FormContext);
9
10 return {
11 value: form.values[name] || '',
12 error: form.errors[name],
13 onChange: (e) => form.setValue(name, e.target.value),
14 onBlur: () => form.validateField(name)
15 };
16}
17
18// Komponent pola formularza
19function FormField({ name, label, type = 'text', validate }) {
20 const field = useFormField(name);
21 const form = use(FormContext);
22
23 // Rejestracja walidatora
24 if (validate && !form.validators[name]) {
25 form.registerValidator(name, validate);
26 }
27
28 return (
29 <div style={{ marginBottom: '15px' }}>
30 <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
31 {label}
32 </label>
33 <input
34 type={type}
35 value={field.value}
36 onChange={field.onChange}
37 onBlur={field.onBlur}
38 style={{
39 width: '100%',
40 padding: '8px',
41 border: `1px solid ${field.error ? '#dc3545' : '#ddd'}`,
42 borderRadius: '4px'
43 }}
44 />
45 {field.error && (
46 <div style={{ color: '#dc3545', fontSize: '14px', marginTop: '5px' }}>
47 {field.error}
48 </div>
49 )}
50 </div>
51 );
52}
53
54// Provider formularza
55function FormProvider({ children, onSubmit }) {
56 const [values, setValues] = useState({});
57 const [errors, setErrors] = useState({});
58 const [validators] = useState({});
59
60 const setValue = (name, value) => {
61 setValues(prev => ({ ...prev, [name]: value }));
62 // Czyść błąd przy zmianie wartości
63 setErrors(prev => ({ ...prev, [name]: '' }));
64 };
65
66 const registerValidator = (name, validate) => {
67 validators[name] = validate;
68 };
69
70 const validateField = (name) => {
71 if (validators[name]) {
72 const error = validators[name](values[name]);
73 setErrors(prev => ({ ...prev, [name]: error }));
74 return !error;
75 }
76 return true;
77 };
78
79 const validateAll = () => {
80 const newErrors = {};
81 let isValid = true;
82
83 Object.keys(validators).forEach(name => {
84 const error = validators[name](values[name]);
85 if (error) {
86 newErrors[name] = error;
87 isValid = false;
88 }
89 });
90
91 setErrors(newErrors);
92 return isValid;
93 };
94
95 const handleSubmit = (e) => {
96 e.preventDefault();
97 if (validateAll()) {
98 onSubmit(values);
99 }
100 };
101
102 const formValue = {
103 values,
104 errors,
105 validators,
106 setValue,
107 registerValidator,
108 validateField,
109 handleSubmit
110 };
111
112 return (
113 <FormContext.Provider value={formValue}>
114 <form onSubmit={handleSubmit}>
115 {children}
116 </form>
117 </FormContext.Provider>
118 );
119}
120
121// Przykład użycia
122function RegistrationFormExample() {
123 const handleSubmit = (values) => {
124 console.log('Form submitted:', values);
125 alert('Rejestracja zakończona sukcesem!');
126 };
127
128 return (
129 <div style={{ maxWidth: '400px', margin: '0 auto', padding: '20px' }}>
130 <h2>Formularz rejestracji</h2>
131
132 <FormProvider onSubmit={handleSubmit}>
133 <FormField
134 name="email"
135 label="Email"
136 type="email"
137 validate={(value) => {
138 if (!value) return 'Email jest wymagany';
139 if (!/S+@S+.S+/.test(value)) return 'Nieprawidłowy email';
140 return '';
141 }}
142 />
143
144 <FormField
145 name="password"
146 label="Hasło"
147 type="password"
148 validate={(value) => {
149 if (!value) return 'Hasło jest wymagane';
150 if (value.length < 8) return 'Hasło musi mieć minimum 8 znaków';
151 return '';
152 }}
153 />
154
155 <FormField
156 name="confirmPassword"
157 label="Potwierdź hasło"
158 type="password"
159 validate={(value) => {
160 const form = use(FormContext);
161 if (value !== form.values.password) return 'Hasła nie są identyczne';
162 return '';
163 }}
164 />
165
166 <button
167 type="submit"
168 style={{
169 width: '100%',
170 padding: '10px',
171 backgroundColor: '#007bff',
172 color: 'white',
173 border: 'none',
174 borderRadius: '4px',
175 fontSize: '16px',
176 cursor: 'pointer'
177 }}
178 >
179 Zarejestruj się
180 </button>
181 </FormProvider>
182 </div>
183 );
184}1// ✅ DOBRZE - warunkowe użycie
2function ConditionalData({ showDetails }) {
3 if (showDetails) {
4 const details = use(detailsPromise);
5 return <DetailView details={details} />;
6 }
7 return <Summary />;
8}
9
10// ✅ DOBRZE - w pętlach
11function MultipleResources({ resourceIds }) {
12 const resources = resourceIds.map(id => {
13 const promise = fetchResource(id);
14 return use(promise);
15 });
16 return <ResourceList resources={resources} />;
17}
18
19// ❌ ŹLE - niepotrzebne użycie dla prostych wartości
20function BadExample() {
21 const value = use(Promise.resolve(42)); // Niepotrzebne!
22 return <div>{value}</div>;
23}1// Implementacja prostego cache
2const cache = new Map();
3
4function useCachedResource(key, fetcher) {
5 if (!cache.has(key)) {
6 cache.set(key, fetcher());
7 }
8
9 const promise = cache.get(key);
10 const resource = use(promise);
11
12 return resource;
13}
14
15// Użycie
16function CachedDataComponent({ userId }) {
17 const userData = useCachedResource(
18 `user-${userId}`,
19 () => fetchUser(userId)
20 );
21
22 return <UserProfile user={userData} />;
23}1// Wrapper z retry logic
2function createRetryablePromise(fetcher, maxRetries = 3) {
3 return async () => {
4 let lastError;
5
6 for (let i = 0; i < maxRetries; i++) {
7 try {
8 return await fetcher();
9 } catch (error) {
10 lastError = error;
11 if (i < maxRetries - 1) {
12 // Exponential backoff
13 await new Promise(resolve =>
14 setTimeout(resolve, Math.pow(2, i) * 1000)
15 );
16 }
17 }
18 }
19
20 throw lastError;
21 };
22}
23
24// Użycie
25function RobustDataFetching() {
26 const dataPromise = createRetryablePromise(
27 () => fetch('/api/critical-data').then(res => res.json())
28 )();
29
30 return (
31 <ErrorBoundary>
32 <Suspense fallback={<div>Ładowanie...</div>}>
33 <DataComponent dataPromise={dataPromise} />
34 </Suspense>
35 </ErrorBoundary>
36 );
37}Hook
use otwiera nowe możliwości w React, pozwalając na bardziej elastyczne zarządzanie asynchronicznymi danymi i kontekstem. Jego unikalna zdolność do bycia używanym warunkowo i w pętlach czyni go potężnym narzędziem dla zaawansowanych aplikacji.