useRef

ما هي useRef؟

  • هو هوك يُرجع لك كائنًا ثابت الهوية عبر كل عمليات الرندر: { current: initialValue }.
  • تحديث ref.current لا يسبب إعادة رندر.
  • يستخدم إمّا للإمساك بـ عناصر DOM مباشرة، أو للاحتفاظ بقيم متغيرة عبر الرندرات (timer IDs، آخر قيمة، أي كائن/مثيل…).

التوقيع:

const ref = useRef(initialValue) // ref.current === initialValue في أول رندر
JSX
متى أستخدمه بدل useState؟
  • عندما تريد تخزين قيمة تتغيّر عبر الزمن بدون أن تُعيد رندر (مثل عدّاد داخلي، مؤقت، كاش لكائن).
  • للوصول إلى DOM: تركيز حقل إدخال، قياس عنصر، التمرير إليه…
  • عند التعامل بـ واجهات أمرية Imperative APIs (مع مكتبات طرف ثالث أو components أطفال).
فروقات مهمة: useRef vs useState
  • إعادة الرندر: تغيير state يعيد الرندر؛ تغيير ref.current لا.
  • الهدف: state لبيانات تؤثر على العرض؛ ref لقيم داخلية/DOM/مؤقتات/مثيلات.
  • التتبّع: React لا يراقب ref.current. لو أردت أن ينعكس التغيير على الـ UI، انقلها لـ state أو انسخها لـ state عند الحاجة.
أفضل الممارسات
  • افصل بين عرض (state) وتفاصيل تنفيذية (ref).
  • تحقّق من null قبل استخدام ref.current (خاصة مع DOM).
  • مع StrictMode في التطوير، قد يُعاد التثبيت مرتين؛ نظّف أي تأثيرات/مؤقتات بشكل صحيح.
  • لا تستخدم ref لتخزين حالة مشتقة من props/state؛ بدلاً من ذلك احسبها أثناء الرندر أو بـ useMemo.
أخطاء شائعة
  • ❌ الاعتماد على ref لتحديث واجهة المستخدم مباشرة (لن يحدث رندر).
  • ❌ قراءة/تعديل DOM في التابع render؛ استخدم useEffect/useLayoutEffect.
  • ❌ نسيان تنظيف المؤقتات أو الـ observers في cleanup.
  • ❌ استخدام ref بديلاً عن state في حقول متحكَّم بها (controlled inputs).
لمحة سريعة مع TypeScript (اختياري)
const inputRef = useRef<HTMLInputElement | null>(null);
const idRef = useRef<number | null>(null);
const anyRef = useRef<{ count: number } | null>(null);
JSX
أمثلة أساسية:
🧩 المثال 1 — تركيز تلقائي على input بعد الرندر
import { useState, useRef, useEffect } from "react";

export default function AutoFocusInput() {
  const [name, setName] = useState("");
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // يعطي التركيز للحقل عند أول تحميل
  }, []);

  return (
    <div>
      <input
        ref={inputRef}
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="اكتب اسمك..."
      />
      <p>أهلاً {name || "بك"}!</p>
    </div>
  );
}
JSX
🧩 المثال 2 — حفظ القيمة السابقة
import { useState, useEffect, useRef } from "react";

export default function PreviousValueExample() {
  const [count, setCount] = useState(0);
  const prevCount = useRef(0);

  useEffect(() => {
    prevCount.current = count; // حفظ القيمة السابقة بعد كل تحديث
  }, [count]);

  return (
    <div>
      <p>القيمة الحالية: {count}</p>
      <p>القيمة السابقة: {prevCount.current}</p>
      <button onClick={() => setCount(count + 1)}>زيادة</button>
    </div>
  );
}
JSX
🧩 المثال 3 — عدّاد بمؤقت (Timer)
import { useState, useRef, useEffect } from "react";

export default function TimerExample() {
  const [seconds, setSeconds] = useState(0);
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    return () => clearInterval(timerRef.current); // تنظيف المؤقت عند الإزالة
  }, []);

  return (
    <div>
      <p>الوقت: {seconds} ثانية</p>
      <button onClick={() => clearInterval(timerRef.current)}>إيقاف المؤقت</button>
    </div>
  );
}
JSX
🧩 المثال 4 — تمرير تلقائي إلى آخر عنصر (Scroll)
import { useState, useEffect, useRef } from "react";

export default function ScrollToBottom() {
  const [messages, setMessages] = useState(["مرحبا"]);
  const endRef = useRef(null);

  useEffect(() => {
    endRef.current.scrollIntoView({ behavior: "smooth" });
  }, [messages]); // يتم التمرير كلما أضفنا رسالة جديدة

  return (
    <div style={{ height: 200, overflow: "auto", border: "1px solid #ccc" }}>
      {messages.map((msg, i) => (
        <p key={i}>{msg}</p>
      ))}
      <div ref={endRef} />
      <button onClick={() => setMessages([...messages, "رسالة جديدة"])}>
        أضف رسالة
      </button>
    </div>
  );
}
JSX
🧩 المثال 5 — عدّاد داخلي لا يعيد الرندر كل مرة
import { useState, useRef, useEffect } from "react";

export default function InternalCounter() {
  const [visibleCount, setVisibleCount] = useState(0);
  const countRef = useRef(0);

  useEffect(() => {
    const id = setInterval(() => {
      countRef.current += 1; // لا يسبب إعادة رندر
      if (countRef.current % 5 === 0) {
        setVisibleCount(countRef.current); // يحدث الرندر كل 5 ثوانٍ فقط
      }
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <p>العدّاد الظاهر: {visibleCount}</p>;
}
JSX