useReducer

ما هي useReducer؟

هي هوك لإدارة الحالة (state) في React عندما تصبح منطقية التحديث معقّدة أو مترابطة. بدل ما تكتب useState كثيرة وتُبعثر منطق التحديث داخل مكوّناتك، تضع قواعد التحديث في دالة واحدة اسمها reducer.

صيغة الاستخدام:

const [state, dispatch] = useReducer(reducer, initialState, init?);
JSX
  • state: الحالة الحالية.
  • dispatch(action): تُرسل “حدث/أمر” يصف ما الذي تريد تغييره.
  • reducer(state, action): دالة نقية تُرجع حالة جديدة بناءً على نوع الأمر والبيانات.
  • initialState: الحالة الابتدائية.
  • init? (اختياري): دالة تهيئة “كسولة” للحالات الكبيرة أو المحسوبة.
تشريح الـ reducer

عادة نستخدم كائن action به type و (اختياريًا) payload:

function reducer(state, action) {
  switch (action.type) {
    case 'SOMETHING_HAPPENED':
      return { ...state, ... } // لا تعدّل state مباشرة
    default:
      return state;
  }
}
JSX

قاعدة ذهبية: لا تُعدِّل state مباشرة (دائمًا أنشئ نسخة جديدة).

متى أستخدم useReducer بدل useState؟

استخدم useReducer عندما:

  1. منطق التحديث معقّد أو هناك عدة أنواع من التعديلات على الحالة (مثل السلة، المعالِجات المتعددة).
  2. توجد علاقات بين أجزاء الحالة (تغيّر شيء يستلزم تحديث شيء آخر).
  3. تريد تجميع منطق الحالة في مكان واحد لسهولة الصيانة والاختبار.
  4. تحتاج تتبّعًا واضحًا للأحداث (مفيد مع سجلّ أو Logger).
  5. عند المقارنة/الاستبدال بـ Redux في مكوّنات صغيرة أو نطاق محلي—useReducer خيار خفيف أنظف.

أما useState فافضل عندما:

  • الحالة بسيطة جدًا وتحديثاتها مباشرة.
  • لا توجد علاقات كثيرة بين حقول الحالة.
فوائد useReducer (مختصر مفيد)
  • تنظيم: توحيد منطق التحديث في دالة واحدة.
  • قابلية الصيانة: يسهل إضافة أحداث جديدة أو تغيير السلوك.
  • سهولة الاختبار: تختبر reducer كوحدة مستقلة.
  • وضوح التدفّق: كل تغيير حالة يحدث عبر dispatch(action) قابل للتتبع.
  • أداء: يُجنّب إعادة إنشاء دوال setState متعددة؛ خصوصًا مع تمرير dispatch لأسفل.
نقاط احترافية (Tips)
  • عدم التعديل المباشر: دائمًا أنشئ نسخًا جديدة ({...state} أو .map/.filter).
  • أنواع الأحداث: اجعل type ثابتًا وواضحًا (سلاسل ثابتة أو enum في TypeScript).
  • تهيئة كسولة: لو الحالة الابتدائية مكلفة حسابيًا: function init(raw) { /* حساب مكلف */ return processed; } const [state, dispatch] = useReducer(reducer, rawInitArg, init);
  • التجميع مع Context: لتشارك الحالة عبر الشجرة:
    • ضع const Store = createContext() ومرّر {state, dispatch} من Provider.
  • تجزئة Reducers: إن كبر المنطق، قسّمه لعدّة reducers وادمج النتائج في أعلى مكوّن.
1) عدّاد: جمع/طرح + تصفير
  • أزرار: +1, -1, تصفير.
  • الـ reducer يستقبل أنواع أحداث واضحة.
import { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return initialState;
    default:
      return state;
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div style={{ fontFamily: "sans-serif", textAlign: "center" }}>
      <h2>العدّاد: {state.count}</h2>
      <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
      <button onClick={() => dispatch({ type: "increment" })} style={{ margin: "0 8px" }}>
        +1
      </button>
      <button onClick={() => dispatch({ type: "reset" })}>تصفير</button>
    </div>
  );
}
JSX

الفكرة: كل تغيير حالة يتم عبر dispatch({type: ...})، والـ reducer هو المكان الوحيد الذي يقرر الحالة الجديدة.

2) فورم بسيط: إرسال بيانات الاسم والإيميل
  • حقول name وemail (متحكَّم بها).
  • زر إرسال يعرض “البيانات المُرسلة” أسفل الفورم (بدون API حقيقي لتبسيط المثال).
  • أزرار: مسح لإعادة ضبط الحقول.

import { useReducer } from "react"
function ReducerForm() {

    const initialState = {
        name: "",
        email: ""
    }

    const reducer = (state, action) => {
        switch (action.type) {
            case "input":
                return {...state , [action.field] : action.value};
            case "reset":
                return initialState;
            default:
                return state;
        }
    }

    const [state, dispatch] = useReducer(reducer, initialState)

    const handelReset = () => {
        dispatch({ type: "reset" });
    }

    const handelChange = (e) => {
        dispatch({ 
            type: "input",
            field: e.target.name,
            value: e.target.value
        
        })
    }


    const handelSubmit = (e)=> {
        e.preventDefault();
        console.log(state)
    }
    return (
     <form action="" onSubmit={handelSubmit}>
           <div>
            <label htmlFor="">Name:</label>
            <input type="text" placeholder="Enter Name" name="name" value={state.name} onChange={handelChange} />
            <br />
            <br />
            <label htmlFor="">Email:</label>
            <input type="text" placeholder="Enter Email" name="email" value={state.email} onChange={handelChange} />
            <br />
            <br />
            <button type="submit">Send Data</button>
            <button type="button" onClick={handelReset}>Reset</button>
        </div>
     </form>
    )
}

export default ReducerForm
JSX
import { useReducer, useState } from "react";

const initialForm = {
  name: "",
  email: "",
};

function formReducer(state, action) {
  switch (action.type) {
    case "updateField": {
      const { field, value } = action.payload;
      return { ...state, [field]: value };
    }
    case "reset":
      return initialForm;
    default:
      return state;
  }
}

export default function SimpleForm() {
  const [form, dispatch] = useReducer(formReducer, initialForm);
  const [submitted, setSubmitted] = useState(null);

  function handleChange(e) {
    const { name, value } = e.target;
    dispatch({ type: "updateField", payload: { field: name, value } });
  }

  function handleSubmit(e) {
    e.preventDefault();
    // هنا “نرسل” البيانات — في مشروع حقيقي ستستدعي API
    setSubmitted(form); // للعرض فقط
  }

  return (
    <div style={{ fontFamily: "sans-serif", maxWidth: 420 }}>
      <h2>نموذج بسيط</h2>

      <form onSubmit={handleSubmit}>
        <label>
          الاسم:
          <input
            name="name"
            value={form.name}
            onChange={handleChange}
            placeholder="اكتب اسمك"
            style={{ display: "block", margin: "6px 0 12px" }}
          />
        </label>

        <label>
          الإيميل:
          <input
            name="email"
            value={form.email}
            onChange={handleChange}
            placeholder="[email protected]"
            style={{ display: "block", margin: "6px 0 12px" }}
          />
        </label>

        <button type="submit">إرسال</button>
        <button
          type="button"
          onClick={() => dispatch({ type: "reset" })}
          style={{ marginLeft: 8 }}
        >
          مسح
        </button>
      </form>

      {submitted && (
        <div style={{ marginTop: 16 }}>
          <h3>البيانات المُرسلة:</h3>
          <pre>{JSON.stringify(submitted, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}
JSX