useEffect و Side Effects — مقدمة React
useEffect و Side Effects
useEffect يُستخدم للعمليات الجانبية مثل: طلبات API، الاشتراكات، تعديل DOM.
الاستخدام الأساسي
import { useEffect, useState } from 'react';
function Component() {
useEffect(() => {
// هذا الكود يُنفذ بعد كل render
console.log("Component rendered!");
});
return <div>محتوى</div>;
}Dependency Array
// يُنفذ بعد كل render
useEffect(() => {
console.log("كل مرة");
});
// يُنفذ مرة واحدة عند التحميل
useEffect(() => {
console.log("مرة واحدة");
}, []);
// يُنفذ عند تغير count
useEffect(() => {
console.log("Count changed:", count);
}, [count]);
// يُنفذ عند تغير أي من القيمتين
useEffect(() => {
console.log("Updated");
}, [value1, value2]);Cleanup Function
useEffect(() => {
// Setup
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
// Cleanup - يُنفذ قبل الـ effect التالي أو عند unmount
return () => {
clearInterval(timer);
};
}, []);جلب البيانات (Data Fetching)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('فشل جلب البيانات');
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // يُنفذ عند تغير userId
if (loading) return <div>جارٍ التحميل...</div>;
if (error) return <div>خطأ: {error}</div>;
if (!user) return <div>لا يوجد مستخدم</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}أمثلة شائعة
1. عنوان الصفحة
function Page({ title }) {
useEffect(() => {
document.title = title;
return () => {
document.title = "Kodax";
};
}, [title]);
return <h1>{title}</h1>;
}2. Event Listeners
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<p>
حجم النافذة: {size.width} × {size.height}
</p>
);
}3. Local Storage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// استخدام
function App() {
const [theme, setTheme] = useLocalStorage('theme', 'dark');
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
الوضع: {theme}
</button>
);
}4. WebSocket
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const ws = new WebSocket(`wss://chat.com/${roomId}`);
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
ws.onopen = () => {
console.log('Connected to room:', roomId);
};
return () => {
ws.close();
};
}, [roomId]);
return (
<div>
{messages.map((msg, i) => (
<p key={i}>{msg.text}</p>
))}
</div>
);
}قواعد مهمة
1. لا تنسَ Cleanup للاشتراكات والـ timers
2. أضف كل Dependencies المستخدمة داخل useEffect
3. تجنب تحديث State داخل useEffect بدون شرط (infinite loop)
4. استخدم multiple useEffect لعمليات مختلفة
import { useState, useEffect } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
// جلب المنشورات
useEffect(() => {
const fetchPosts = async () => {
setLoading(true);
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`
);
const data = await response.json();
setPosts(data);
setLoading(false);
};
fetchPosts();
}, [page]);
// تحديث العنوان
useEffect(() => {
document.title = `صفحة ${page}`;
}, [page]);
if (loading) return <p>جارٍ التحميل...</p>;
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</article>
))}
<button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
السابق
</button>
<span>صفحة {page}</span>
<button onClick={() => setPage(p => p + 1)}>
التالي
</button>
</div>
);
}اختبر فهمك