1663 words
8 minutes
useTransition Hook in React

تخيل معايا تطبيق فيه list كبيرة من المستخدمين وعندك input بحث عشان تقدر تدور على المستخدمين دول بالاسم وتفلترهم بناءا عليه المشكلة الفعلية فين بقي ؟ دلوقتي، React بتعتبر كل تحديث في ال (state) إنه urgent تقدر تقول high priority . بس برضه لو عندك تحديثات بتحصل في نفس الوقت، React هتعمل “batch” للتحديثات دي مع بعضها عشان تمنع الرندرة الكتير اللي ممكن تقلل من الpreformance ورغم كل دا ف التطبيق اللي عملناه دا لو بتتعامل مع قائمة كبيرة، هتلاقي إن أول تحديث للstate (اللي هو التغيير في searchTerm) بيحصل بسرعة، لكن التحديث التاني اللي هو فلترة المستخدمين بياخد وقت أطول شوية. وده معناه إن الكتابة في ال searchTerm هتحس إنها بطيئة، وده طبعًا هيكون مشكلة لل user experience .تقدر تقول إن بيحصل blocking لل UI.

المشكلة اللي بنحلها 🤔#

لما بيكون عندك تطبيق React وفيه عمليات كتير بتحصل مع بعض، زي:

  • تحديث حالة البحث
  • فلترة قائمة كبيرة
  • تحميل بيانات
  • عمليات حسابية معقدة

الـ UI بيبقى “blocked” وده بيخلي التطبيق يحس إنه بطيء.

الحل: useTransition 💡#

طريقة الاستخدام الأساسية#

الـ useTransition هو hook يتيحلك تأخير تنفيذ بعض ال state updates اللي مش ضرورية إنها تحصل في الحال(non-urgent) . الفكرة إنك ممكن تخلي الupadates الأقل أهمية تحصل بعد ما التحديثات الurgent تكون اتنفذت. ال useTransition بترجع array فيها قيمتين

  • أول قيمة ال isPending دي متغير من نوع boolean، بيكون true لما يبدأ الـ startTransition في تنفيذ الـ callback function اللي بتمررها ليه. وبيكون false لما تخلص عملية تنفيذ الـ callback

  • تاني قيمة ال startTransition دي function بتاخد callback function كـ argument. الـ callback function دي بتحط جواها أي تحديثات للstate اللي عايز تقول لreact إنها مش أولوية

import React, { useState, useTransition } from "react";

export default function App({ users }) {
  const [searchTerm, setSearchTerm] = useState("");
  const [filtered, setFiltered] = useState(users);
  const [isPending, startTransition] = useTransition();

  const handleChange = ({ target: { value } }) => {
    setSearchTerm(value);
    startTransition(() => {
      setFiltered(users.filter((item) => item.name.includes(value)));
    });
  };

  return (
    <div className="container">
      {isPending ? <div>جاري التحميل...</div> : <div>{filtered.length} matches</div>}

      <input
        onChange={handleChange}
        value={searchTerm}
        type="text"
        placeholder="اكتب اسم المستخدم"
      />

      <div className="cards">
        {filtered.map((user) => (
          <div className="card" key={user.id}>
            <div className="profile">
              <img src={user.avatar} alt="avatar" />
            </div>
            <div className="body">
              <strong>{user.name}</strong>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

تطبيقات متقدمة 🔥#

1. التعامل مع عدة تحديثات متداخلة#

function ComplexComponent() {
  const [isPending, startTransition] = useTransition();
  const [data, setData] = useState([]);
  const [filters, setFilters] = useState({});
  const [sortConfig, setSortConfig] = useState({});

  const updateDataWithFilters = useCallback((newFilters) => {
    // تحديث فوري للفلاتر
    setFilters(newFilters);
    
    startTransition(() => {
      // عمليات معقدة
      const filteredData = applyFilters(data, newFilters);
      const sortedData = applySorting(filteredData, sortConfig);
      const processedData = applyTransformations(sortedData);
      
      setData(processedData);
    });
  }, [data, sortConfig]);

  // استخدام Loading State بشكل ذكي
  return (
    <div>
      {isPending ? (
        <LoadingIndicator type="overlay" />
      ) : null}
      <DataGrid 
        data={data} 
        className={isPending ? 'opacity-50' : ''}
      />
    </div>
  );
}

بص يا سيدي، الكود ده بيتعامل مع حتة معقدة شوية، وهي لما تيجي تعمل تحديثات كتير ورا بعض على الداتا بتاعتك. خلينا نفهمها حتة حتة:

const [isPending, startTransition] = useTransition();

دي بتقولك “هات لي حالة علشان أعرف إذا في حاجة بتحصل في الخلفية ولا لأ”

const [data, setData] = useState([]);
const [filters, setFilters] = useState({});
const [sortConfig, setSortConfig] = useState({});

هنا عامل 3 متغيرات:

الداتا نفسها الفلاتر اللي هنطبقها طريقة الترتيب

const updateDataWithFilters = useCallback((newFilters) => {

دي فنكشن بتعمل حاجتين مهمين:

بتحط الفلاتر الجديدة على طول (فوري) بعدين بتبدأ تحديث الداتا في الباكجراوند عن طريق startTransition

لما تيجي تحدث الداتا:

startTransition(() => {
  const filteredData = applyFilters(data, newFilters);
  const sortedData = applySorting(filteredData, sortConfig);
  const processedData = applyTransformations(sortedData);
  setData(processedData);
});

هنا بيحصل 3 حاجات ورا بعض:

بيطبق الفلتر الأول بيرتب النتيجة بيعمل أي تحويلات تانية مطلوبة

والحلو في الموضوع إن isPending بيقولك لو في حاجة بتحصل في الخلفية، فممكن تعرض loading state للمستخدم. الفكرة كلها إنك:

بتحدث الفلاتر على طول علشان المستخدم يشوف إن حاجة حصلت بتعمل الشغل التقيل في الخلفية الصفحة مش بتهنج أو تقف المستخدم بيشوف loading state لو في شغل بيحصل

2. إدارة Concurrent Transitions#

function AdvancedSearch() {
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);
  const [activeTransitions, setActiveTransitions] = useState(new Set());

  const search = useCallback((term, filters) => {
    const transitionId = Date.now();
    setActiveTransitions(prev => new Set(prev).add(transitionId));

    startTransition(() => {
      // عملية البحث المعقدة
      performSearch(term, filters).then(data => {
        // تأكد إن ده آخر transition
        setActiveTransitions(prev => {
          const updated = new Set(prev);
          updated.delete(transitionId);
          return updated;
        });

        if (activeTransitions.size === 0) {
          setResults(data);
        }
      });
    });
  }, []);
}

الفكرة إن الكود ده بيتعامل مع حالة لما يكون عندك بحث متكرر وسريع، يعني المستخدم بيكتب كلام كتير ورا بعض.

أول حاجة، بنعمل ID لكل عملية بحث باستخدام Date.now() - يعني timestamp

const transitionId = Date.now();

بنضيف الـ ID ده في مجموعة (Set) اسمها activeTransitions - دي زي شنطة بنحط فيها كل عمليات البحث اللي شغالة حالياً

setActiveTransitions(prev => new Set(prev).add(transitionId));

لما البحث يخلص:

بنشيل الـ ID بتاع العملية دي من الشنطة بنتأكد لو دي آخر عملية (يعني الشنطة فاضية) لو فاضية، ساعتها بس نعرض النتايج

يعني باختصار: الكود ده بيضمن إن النتايج اللي هتظهر للمستخدم هي نتايج آخر بحث عمله، مش أي بحث في النص. ده بيمنع مشكلة “Race Condition” - يعني لو المستخدم كتب “محمد” وبعدين “أحمد” ورا بعض، مش هيحصل لخبطة في النتايج. زي ما تقول كده: بتأكد إن اللي هيوصل الآخر هو اللي يفوز 🏃‍♂️🏆

3. التعامل مع Suspense#

function DataLoader() {
  // isPending دي بتقولنا لو في تحميل شغال ولا لأ
  // startTransition دي الفانكشن اللي بنقول فيها "خد وقتك في التحميل"
  const [isPending, startTransition] = useTransition();
  
  // resource ده المكان اللي بنخزن فيه الداتا
  const [resource, setResource] = useState(initialResource);

  // فانكشن التحميل
  const loadData = () => {
    startTransition(() => {
      // بنجيب الداتا الجديدة (fetchData) ونحطها في الـ resource
      setResource(fetchData());
    });
  };

  return (
    <div>
      {/* Suspense ده زي "غطا" بيظهر Spinner لحد ما الداتا تيجي */}
      <Suspense fallback={<Spinner />}>
        <DataComponent resource={resource} />
      </Suspense>
      
      {/* لو لسه في تحميل، نعرض مؤشر التقدم */}
      {isPending && <ProgressIndicator />}
    </div>
  );
}

ده كومبوننت بيحمل داتا، وبيستخدم حاجتين مهمين:

  • useTransition - عشان نتحكم في توقيت تحميل الداتا

  • Suspense - ده بيساعدنا نعرض حاجة مؤقتة لحد ما الداتا تيجي

Performance Patterns 📈#

1. Debounced Transitions#

function useDebouncedTransition(delay = 300) {
  const [isPending, startTransition] = useTransition();
  const debouncedTransition = useCallback(
    debounce((callback) => {
      startTransition(callback);
    }, delay),
    []
  );

  return [isPending, debouncedTransition];
}

تخيل معايا السيناريو ده: أنت عندك search box وكل ما المستخدم يكتب حرف، بتعمل بحث. طب لو المستخدم بيكتب بسرعة؟ هتعمل بحث مع كل حرف؟ ده هيبوظ الدنيا! وهنا بييجي دور الـ Debounced Transitions. ده زي “المنظم” بتاع العمليات دي.

خليني أشرحها بمثال من الحياة:

تخيل إنك في تاكسي والراكب بيقولك “يمين… لا شمال… لا اطلع على طول” لو نفذت كل أمر على طول، هتلف وتدور وتتعب نفسك الأحسن تستنى شوية (delay) لحد ما الراكب يستقر على قراره النهائي وبعدين تنفذ

نفس الفكرة هنا:

بدل ما تنفذ الـ transition مع كل تغيير بتستنى فترة صغيرة (300 مللي ثانية) لو مفيش تغييرات جديدة، تنفذ آخر تغيير لو في تغييرات جديدة، تبدأ العد من الأول

فوايد الموضوع:

بيحسن الـ performance بتاع التطبيق بيقلل عدد العمليات اللي بتحصل بيخلي التطبيق أسرع وأكفأ يعني باختصار: بدل ما تعمل كل حاجة على طول، خليك زي الحكيم - استنى شوية وشوف آخر الكلام إيه وبعدين نفذ 😉

2. Prioritized Updates#

تخيل إنك مدير مطعم وعندك طلبات كتير جاية في نفس الوقت:

function PrioritizedList() {
  const [isPending, startTransition] = useTransition();
  // دول زي قايمتين: واحدة للطلبات العادية وواحدة للطلبات المستعجلة
  const [visibleItems, setVisibleItems] = useState([]); // الطلبات العادية
  const [urgentItems, setUrgentItems] = useState([]); // الطلبات المستعجلة

  const updateList = (newData) => {
    // هنا بنفرز الطلبات المستعجلة ونعملها فوراً
    // زي لما حد يطلب "عايز الأكل ضروري"
    const urgent = newData.filter(item => item.priority === 'high');
    setUrgentItems(urgent);

    // الطلبات العادية، نأجلها شوية
    // زي الناس اللي قاعدة عادي ومش مستعجلة
    startTransition(() => {
      const normal = newData.filter(item => item.priority !== 'high');
      setVisibleItems(normal);
    });
  };
}

يعني باختصار:

الحاجات المهمة (urgent) بتتنفذ على طول الحاجات العادية بتتنفذ بعد كده

مثال من الواقع:

لما ييجي واحد يقولك “محتاج الأكل دلوقتي حالاً” ← ده urgent وواحد تاني يقولك “خد راحتك” ← ده normal

فايدة الموضوع ده:

بيخلي التطبيق أسرع في التعامل مع الحاجات المهمة مش بيعطل الـ UI بيدي أولوية للحاجات اللي محتاجة تتنفذ بسرعة

زي ما تقول كده: “الأهم فالمهم”

نصائح متقدمة 🎯#

1. متى تستخدم useTransition#

✅ استخدمه مع:

  • فلترة قوائم كبيرة
  • عمليات حسابية معقدة
  • تحميل بيانات كبيرة
  • تحديثات UI المعقدة

❌ متستخدموش مع:

  • التحديثات البسيطة
  • العمليات السريعة
  • تحديثات الـ input المباشرة
  • التفاعلات اللي محتاجة response فوري

2. Memory Considerations#

function MemoryEfficientTransition() {
  // useRef ده زي "علامة" بنحطها على آخر عملية شغالة
  // زي ما تقول كده بنعلم على آخر طبق بيتعمل في المطبخ
  const transitionCallback = useRef(null);
  const [isPending, startTransition] = useTransition();

  const handleUpdate = (data) => {
    // قبل ما نبدأ طبق جديد، بنشوف لو في طبق لسه متعملش
    if (transitionCallback.current) {
      // لو لقينا طبق قديم مخلصش، بنوقفه
      // زي ما تقول كده "بلاش كمل الطبق القديم ده"
      cancelCallback(transitionCallback.current);
    }

    // نبدأ في الطبق الجديد
    startTransition(() => {
      transitionCallback.current = processLargeData(data);
    });
  };
}

خليني أشرحها بمثال من المطبخ:

تخيل إنك بتعمل سندوتشات جه طلب جديد قبل ما تخلص السندوتش القديم بدل ما تكمل السندوتش القديم وتضيع وقت ومكونات بترمي اللي في إيدك وتبدأ في الطلب الجديد

ليه بنعمل كده؟

عشان منضيعش موارد (ذاكرة) على حاجات مش هنستخدمها بنوفر في استهلاك الجهاز بنمنع تراكم العمليات القديمة

يعني باختصار:

بنحتفظ بـ reference للعملية الحالية لو جه تحديث جديد، بنلغي القديم نبدأ العملية الجديدة وهكذا…

زي ما تقول كده: “متمسكش العصاية من النصين” - يا إما تركز على الجديد يا إما على القديم، مش الاتنين مع بعض 😉

Debug Patterns 🐛#

1. Transition Logging#

function useTrackedTransition() {
  const [isPending, startTransition] = useTransition();
  
  const trackedStartTransition = useCallback((callback) => {
    console.time('transition');
    startTransition(() => {
      callback();
      console.timeEnd('transition');
    });
  }, [startTransition]);

  return [isPending, trackedStartTransition];
}

دا بيعمل تايمر كدا عشان يقيس الوقت اللي بياخده كل تغيير (transition) في التطبيق زي ما تقول بيحط ساعة قدام كل حاجة عشان يشوف استغرقت قد ايه لما التغيير يخلص، بيطبع في الكونسول الوقت اللي استغرقه

2. Performance Monitoring#

function useMonitoredTransition() {
  const [isPending, startTransition] = useTransition();
  const transitions = useRef({ count: 0, totalTime: 0 });

  useEffect(() => {
    if (!isPending) {
      // تسجيل إحصائيات الأداء
      logPerformanceMetrics(transitions.current);
    }
  }, [isPending]);

  return [isPending, startTransition];
}

دا شغال في الخلفية بيراقب أداء التطبيق بيعد عدد التغييرات اللي حصلت بيحسب الوقت الكلي اللي استغرقته التغييرات دي وبعدين بيبعت المعلومات دي للوج (log) عشان تقدر تشوفها وتحلل أداء التطبيق

يعني باختصار، الاتنين دول أدوات بتساعدك تعرف:

كل تغيير في التطبيق بياخد وقت قد ايه إيه المشاكل اللي ممكن تكون موجودة في الأداء وإزاي تحسن سرعة التطبيق

دا زي ما تقول عداد السرعة في العربية - بيوريك التطبيق ماشي بسرعة قد ايه وفين الأماكن اللي محتاجة تظبطها.

Join our whatsapp group here
My Channel here