Hooks الأخرى — مقدمة React
Hooks الأخرى المهمة
useContext - مشاركة البيانات
import { createContext, useContext, useState } from 'react';
// إنشاء Context
const ThemeContext = createContext();
// Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
const toggleTheme = () => {
setTheme(prev => prev === 'dark' ? 'light' : 'dark');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// استخدام في أي مكون
function Button() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
className={`btn btn-${theme}`}
>
الوضع: {theme}
</button>
);
}
// App
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<Button />
</ThemeProvider>
);
}
useRef - مرجع للعناصر
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
// التركيز على الحقل عند التحميل
inputRef.current.focus();
}, []);
return (
<input
ref={inputRef}
placeholder="اكتب هنا..."
/>
);
}
// حفظ قيمة بدون إعادة render
function Timer() {
const countRef = useRef(0);
const [display, setDisplay] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
countRef.current += 1;
console.log(countRef.current); // يتحدث
}, 1000);
return () => clearInterval(timer);
}, []);
return (
<div>
<p>العداد الداخلي: {countRef.current}</p>
<button onClick={() => setDisplay(countRef.current)}>
عرض القيمة الحالية
</button>
<p>المعروض: {display}</p>
</div>
);
}
useMemo - تخزين القيم المحسوبة
import { useMemo, useState } from 'react';
function ExpensiveList({ items, filter }) {
// تُنفذ فقط عند تغير items أو filter
const filteredItems = useMemo(() => {
console.log("Filtering...");
return items.filter(item =>
item.name.includes(filter)
);
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// مثال: حساب معقد
function Calculator({ numbers }) {
const sum = useMemo(() => {
return numbers.reduce((acc, n) => acc + n, 0);
}, [numbers]);
const average = useMemo(() => {
return sum / numbers.length;
}, [sum, numbers.length]);
return (
<div>
<p>المجموع: {sum}</p>
<p>المتوسط: {average}</p>
</div>
);
}
useCallback - تخزين الدوال
import { useCallback, useState, memo } from 'react';
// مكون ابن مُحسّن
const Button = memo(({ onClick, children }) => {
console.log("Button rendered:", children);
return <button onClick={onClick}>{children}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// ❌ بدون useCallback - تُنشأ دالة جديدة كل render
const handleClick = () => setCount(c => c + 1);
// ✅ مع useCallback - نفس الدالة
const handleClickMemo = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<p>العداد: {count}</p>
<Button onClick={handleClickMemo}>زيادة</Button>
</div>
);
}
useReducer - للـ State المعقد
import { useReducer } from 'react';
// الحالة الأولية
const initialState = {
items: [],
loading: false,
error: null
};
// Reducer Function
function reducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, items: action.payload };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
case 'DELETE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
default:
return state;
}
}
function ItemList() {
const [state, dispatch] = useReducer(reducer, initialState);
const fetchItems = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('/api/items');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
const deleteItem = (id) => {
dispatch({ type: 'DELETE_ITEM', payload: id });
};
return (
<div>
{state.loading && <p>جارٍ التحميل...</p>}
{state.error && <p>خطأ: {state.error}</p>}
<ul>
{state.items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => deleteItem(item.id)}>حذف</button>
</li>
))}
</ul>
</div>
);
}
Custom Hooks
// Hook مخصص لجلب البيانات
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// استخدام
function Users() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <p>جارٍ التحميل...</p>;
if (error) return <p>خطأ: {error}</p>;
return (
<ul>
{users?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
import { useState, useContext, createContext, useMemo } from 'react';
// Auth Context
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
const value = useMemo(() => ({
user,
isLoggedIn: !!user,
login,
logout
}), [user]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// Hook مخصص
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// استخدام
function Profile() {
const { user, isLoggedIn, logout } = useAuth();
if (!isLoggedIn) {
return <p>الرجاء تسجيل الدخول</p>;
}
return (
<div>
<h2>مرحباً {user.name}</h2>
<button onClick={logout}>تسجيل الخروج</button>
</div>
);
}
اختبر فهمك