Kodax

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>
  );
}
اختبر فهمك