ما هي 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