---
title: 'React شرح useEffectEvent: إيقاف إعادة تشغيل الـ'
description: 'شرح تطبيق عملي لـ React useEffectEvent: إصلاح الـ stale closures، ومنع إعادة تشغيل useEffect عند تغيير الـ props، والانتقال من useCallback. كود قابل للتشغيل لعام 2026.'
url: https://nerdleveltech.com/ar/react-useeffectevent-stop-effect-reruns-tutorial
language: ar
date: '2026-06-08'
category: web-frontend
tags:
  - react
  - useeffectevent
  - useeffect
  - react-19
  - hooks
  - typescript
  - eslint
  - tutorial
  - web-frontend
markdown_url: https://nerdleveltech.com/ar/react-useeffectevent-stop-effect-reruns-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/react-useeffectevent-stop-effect-reruns-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# React شرح useEffectEvent: إيقاف إعادة تشغيل الـ

تسمح `useEffectEvent`، المستقرة منذ React 19.2، لـ Effect بقراءة أحدث الـ props والـ state دون إدراجها كاعتمادات (dependencies) — لذا فإن تغيير السمة (theme) أو الإعدادات لم يعد يؤدي إلى قطع اتصال الـ WebSocket أو المؤقت. يثبت هذا الدليل العملي لـ React useEffectEvent الحل من خلال اختبارات قابلة للتشغيل.

## ملخص

يبني هذا الدليل التعليمي لـ React useEffectEvent مكونًا صغيرًا لبث المقاييس المباشرة بثلاث طرق: النسخة الساذجة (naive) التي تعيد الاتصال عند كل تغيير للسمة، والنسخة التي تم كتم تحذيرات الـ lint فيها والتي تعرض بيانات قديمة، ونسخة `useEffectEvent` التي لا تفعل أيًا منهما. يتم تثبيت كل سلوك بواسطة اختبار Vitest يمكنك تشغيله بنفسك، ورسائل الـ lint المقتبسة هي مخرجات `eslint-plugin-React-hooks@7.1.1` حرفيًا. الوقت الإجمالي: حوالي 20 دقيقة. تم تنفيذ كل شيء مقابل `React@19.2.7` في يوم الكتابة.

## ما ستتعلمه

- لماذا يعيد `useEffect` التشغيل عند كل تغيير للـ prop — ومتى يكون ذلك خطأً وليس ميزة
- لماذا يؤدي كتم `exhaustive-deps` إلى استبدال تكرار إعادة الاتصال بإغلاقات قديمة (stale closures)
- كيفية إصلاح كلتا المشكلتين باستخدام `useEffectEvent`، خطوة بخطوة
- كيفية ضبط الـ linter بحيث يفرض قواعد Effect Event نيابة عنك
- متى تستخدم `useEffectEvent` مقابل `useCallback` (جدول القرار)
- كيفية التحقق من السلوك بالاختبارات بدلاً من الوثوق بمنشور مدونة

## المتطلبات الأساسية

- Node.js 24 LTS (يعمل أيضًا Node 20.19+ أو 22.12+ — وهذا هو الحد الأدنى الذي يتطلبه Vite 8 إذا قمت لاحقًا بوضع المكون في تطبيق Vite[^5])
- `React@19.2.7` و `React-dom@19.2.7` — تم إطلاق `useEffectEvent` كـ API مستقر في React 19.2 (1 أكتوبر 2025)[^2]، لذا فإن أي إصدار 19.2.x سيعمل؛ هذا المنشور يثبت الإصدار الحالي على npm[^4]
- TypeScript 6.0.3، Vitest 4.1.8، `@testing-library/React@16.3.2`، jsdom 29.1.1
- ESLint 10.4.1 مع `eslint-plugin-React-hooks@7.1.1` و `TypeScript-eslint@8.60.1`

قم بإعداد مشروع بسيط (لا حاجة لخادم تطوير — تعمل أداة الإثبات بدون واجهة رسومية):

```bash
mkdir metrics-feed && cd metrics-feed
npm init -y
npm install --save-exact React@19.2.7 React-dom@19.2.7 \
  TypeScript@6.0.3 @types/React@19.2.17 @types/React-dom@19.2.3 \
  vitest@4.1.8 @testing-library/React@16.3.2 jsdom@29.1.1 \
  eslint@10.4.1 eslint-plugin-React-hooks@7.1.1 TypeScript-eslint@8.60.1
```

اضبط `"type": "module"` في ملف `package.json`، بالإضافة إلى ثلاثة سكربتات: `"test": "vitest run"`، و `"typecheck": "tsc --noEmit"`، و `"lint": "eslint src"`. إذا كنت تفضل العمل داخل تطبيق كامل، فإن الهيكل القياسي هو `npm create vite@latest metrics-feed -- --template React-ts` (create-vite 9.0.7)[^5] — كود المكون أدناه متطابق في كلتا الحالتين.

`vitest.config.ts`:

```ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
  },
});
```

`tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "React-jsx",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": ["vitest/globals"]
  },
  "include": ["src", "tests"]
}
```

## الخطوة 1 — اتصال وهمي يمكنك مراقبته

لماذا يعيد `useEffect` التشغيل عند كل تغيير للـ prop؟ لأن كل قيمة يقرأها جسم الـ Effect يجب أن تكون اعتمادًا (dependency)، ويقوم React بإعادة مزامنة الـ Effect كلما تغير أي اعتماد. لرؤية ذلك يحدث، نحتاج إلى نظام خارجي تكون دورة حياته مرئية. تسجل هذه الوحدة كل اتصال وقطع اتصال في سجل مصدّر — يمكن لواجهة المستخدم عرضه، ويمكن للاختبارات التأكد منه.

`src/connection.ts`:

```ts
export type ConnectionEvent = 'connected';

export interface Connection {
  on(event: ConnectionEvent, handler: () => void): void;
  connect(): void;
  disconnect(): void;
}

// مرئي لواجهة المستخدم وللاختبارات: يتم تسجيل كل اتصال/قطع اتصال هنا.
export const connectionLog: string[] = [];

const ACK_DELAY_MS = 300;

export function createConnection(source: string): Connection {
  let onConnected: (() => void) | null = null;
  let ackTimer: ReturnType<typeof setTimeout> | null = null;

  return {
    on(event, handler) {
      if (event === 'connected') {
        onConnected = handler;
      }
    },
    connect() {
      connectionLog.push(`connect:${source}`);
      ackTimer = setTimeout(() => {
        onConnected?.();
      }, ACK_DELAY_MS);
    },
    disconnect() {
      if (ackTimer !== null) {
        clearTimeout(ackTimer);
      }
      connectionLog.push(`disconnect:${source}`);
    },
  };
}
```

تأخير تأكيد الاتصال (acknowledgment) البالغ 300 مللي ثانية مهم: فهو يخلق نافذة حيث يمكن أن تتغير الـ props *بين* الاتصال واستدعاء رد الاتصال "connected" — وهو بالضبط المكان الذي تختبئ فيه الإغلاقات القديمة.

## الخطوة 2 — النسخة الساذجة: useEffect يعيد التشغيل عند كل تغيير للـ prop

يشترك المكون في `source` للمقاييس ويعرض إشعارًا (toast) عند الاتصال. يذكر الإشعار الـ `theme` الحالي. Effect واحد، قيمتان — لكنهما مختلفتان في النوع: `source` هو *منطق الـ Effect* (تغييره يجب أن يعيد الاتصال)، بينما يتم قراءة `theme` فقط بواسطة *الحدث* الذي ينطلق عندما يتم تأكيد الاتصال.

`src/MetricsFeedNaive.tsx`:

```tsx
import { useEffect, useState } from 'React';
import { createConnection } from './connection';

type Theme = 'dark' | 'light';

export function MetricsFeedNaive({ source, theme }: { source: string; theme: Theme }) {
  const [toast, setToast] = useState('');

  useEffect(() => {
    const connection = createConnection(source);
    connection.on('connected', () => {
      setToast(`Connected to ${source} (${theme} theme)`);
    });
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [source, theme]); // theme مطلوب هنا -- وهذا هو الخطأ

  return (
    <section className={theme}>
      <h2>Live metrics: {source}</h2>
      {toast && <p role="status">{toast}</p>}
    </section>
  );
}
```

مصفوفة الاعتمادات *صحيحة* وفقًا للقواعد — يتم قراءة `theme` داخل الـ Effect، لذا يتطلبه `exhaustive-deps`. وهذا هو بالضبط المشكلة: تبديل السمة يؤدي إلى قطع الاتصال وإعادة بنائه.

حان الوقت لإثبات ذلك. أنشئ `tests/feed.test.tsx` بهذا الهيكل — فهو يستورد جميع متغيرات المكونات الثلاثة مقدمًا (ستنشئ المتغيرين المتبقيين في الخطوتين 3 و 4؛ تعمل المجموعة في مرحلة التحقق)، ويعيد ضبط السجل قبل كل اختبار، ويستخدم مؤقتات وهمية حتى نتحكم بالضبط في وقت انطلاق تأكيد الـ ack البالغ 300 مللي ثانية:

```tsx
import { act cleanup render screen } from '@testing-library/React';
import { useEffectEvent } from 'React';
import { afterEach beforeEach expect test vi } from 'vitest';
import { connectionLog } from '../src/connection';
import { MetricsFeed } from '../src/MetricsFeed';
import { MetricsFeedNaive } from '../src/MetricsFeedNaive';
import { MetricsFeedStale } from '../src/MetricsFeedStale';

beforeEach(() => {
  vi.useFakeTimers();
  connectionLog.length = 0;
});

afterEach(() => {
  cleanup();
  vi.useRealTimers();
});
```

الاختبار الأول يثبت تكرار إعادة الاتصال:

```tsx
test('naive: a theme change tears down and recreates the connection' () => {
  const { rerender } = render(<MetricsFeedNaive source="API" theme="dark" />);
  expect(connectionLog).toEqual(['connect:API']);

  rerender(<MetricsFeedNaive source="API" theme="light" />);
  expect(connectionLog).toEqual(['connect:API' 'disconnect:API' 'connect:API']);
});
```

استبدل الوهمي بـ WebSocket حقيقي وسيكون هذا هو الخطأ الكلاسيكي "الـ socket الخاص بي يعيد الاتصال عند كل render" — تبديل الإعدادات، أو تغيير اللغة، أو أي prop تجميلي يقطع الاتصال ويعيد بناءه من تحت مستخدميك.

## الخطوة 3 — الفخ: كتم قاعدة الـ lint يخلق إغلاقًا قديمًا

الـ "حل" المغري هو حذف `theme` من المصفوفة وإسكات الـ linter. انسخ `MetricsFeedNaive.tsx` إلى `src/MetricsFeedStale.tsx`، وأعد تسمية المكون إلى `MetricsFeedStale`، وغير فقط الأسطر الختامية للـ Effect:

```tsx
    // eslint-disable-next-line React-hooks/exhaustive-deps
  } [source]); // تم حذف theme: لا يوجد تكرار في إعادة الاتصال، لكن الإغلاق يصبح قديمًا
```

يختفي تكرار إعادة الاتصال — ويظهر خطأ مختلف. تم تشغيل الـ Effect مرة واحدة، عندما كان `theme` هو `dark`. قام معالج `connected` بالإغلاق على `theme` الخاص بأول render ولم يرَ أيًا غيره أبدًا. يثبت الاختبار أن الإشعار يكذب:

```tsx
test('stale: suppressing the dep keeps the connection but shows the OLD theme' () => {
  const { rerender } = render(<MetricsFeedStale source="API" theme="dark" />);
  rerender(<MetricsFeedStale source="API" theme="light" />);
  expect(connectionLog).toEqual(['connect:API']); // لا يوجد تكرار...

  act(() => {
    vi.advanceTimersByTime(300);
  });
  // ...لكن الإغلاق التقط سمة الـ render الأول
  expect(screen.getByRole('status').textContent).toBe('Connected to API (dark theme)');
});
```

واجهة المستخدم في الوضع الفاتح (light)؛ والإشعار يقول الداكن (dark). تشير ملاحظات إصدار فريق React إلى هذا النمط بالضبط: "معظم المستخدمين يقومون فقط بتعطيل قاعدة الـ lint واستبعاد الاعتماد. لكن ذلك قد يؤدي إلى أخطاء."[^2]

## الخطوة 4 — الإصلاح: فصل الحدث عن الـ Effect باستخدام useEffectEvent

تستخرج `useEffectEvent` الجزء غير التفاعلي إلى *Effect Event* — وهي وظيفة، وفقًا للوثائق، "تصل دائمًا إلى أحدث القيم المعتمدة من الـ render في وقت الاستدعاء" ويتم استبعادها من اعتمادات الـ Effect.[^1] (يتم تغطية التفكير وراء هذا الفصل بعمق في دليل React "فصل الأحداث عن الـ Effects".[^6]) توضح ملاحظات الإصدار ذلك ببساطة: "على غرار أحداث الـ DOM، ترى أحداث الـ Effect دائمًا أحدث الـ props والـ state."[^2]

`src/MetricsFeed.tsx`:

```tsx
import { useEffect useEffectEvent useState } from 'React';
import { createConnection } from './connection';

type Theme = 'dark' | 'light';

export function MetricsFeed({ source theme }: { source: string; theme: Theme }) {
  const [toast setToast] = useState('');

  const onConnected = useEffectEvent((connectedTo: string) => {
    setToast(`Connected to ${connectedTo} (${theme} theme)`);
  });

  useEffect(() => {
    const connection = createConnection(source);
    connection.on('connected' () => {
      onConnected(source);
    });
    connection.connect();
    return () => {
      connection.disconnect();
    };
  } [source]);

  return (
    <section className={theme}>
      <h2>Live metrics: {source}</h2>
      {toast && <p role="status">{toast}</p>}
    </section>
  );
}
```

ثلاثة أشياء يجب ملاحظتها. القيمة التفاعلية (`source`) تظل اعتمادًا — تغييرها لا يزال يعيد الاتصال. القيمة غير التفاعلية (`theme`) انتقلت إلى Effect Event ويتم قراءتها طازجة في وقت الاستدعاء. و `onConnected` نفسها *ليست* في المصفوفة: يتم استبعاد أحداث الـ Effect من الاعتمادات حسب التصميم، والـ linter يعرف ذلك.[^1]

كلا نصفي الإصلاح قابلان للاختبار الآن:

```tsx
test('useEffectEvent: no reconnect AND the latest theme' () => {
  const { rerender } = render(<MetricsFeed source="API" theme="dark" />);
  rerender(<MetricsFeed source="API" theme="light" />);
  expect(connectionLog).toEqual(['connect:API']); // اتصال واحد

  act(() => {
    vi.advanceTimersByTime(300);
  });
  expect(screen.getByRole('status').textContent).toBe('Connected to API (light theme)');
});

test('useEffectEvent: changing source still reconnects (stays reactive)' () => {
  const { rerender } = render(<MetricsFeed source="API" theme="dark" />);
  rerender(<MetricsFeed source="db" theme="dark" />);
  expect(connectionLog).toEqual(['connect:API' 'disconnect:API' 'connect:db']);
});
```

اتصال واحد، سمة حديثة، وتفاعلية محفوظة حيث تنتمي.

## الخطوة 5 — ضبط الـ linter بحيث تفرض القواعد نفسها

تُفرض قيود Effect Event بواسطة `eslint-plugin-React-hooks`[^3] — ولكن فقط إذا تم تحميل إعداداتك (config) بالفعل. الإصدار 7.1.1 يعرض كلاً من الإعدادات المسبقة (presets) القديمة والمسطحة (flat)، وقد أزال ESLint 10 نظام `.eslintrc` القديم تماماً[^7] — الإعداد المسطح (flat config) هو الوحيد الذي يقبله — لذا يجب عليك استخدام إعدادات `flat` المسبقة. يشير ملف README الخاص بالحزمة لمستخدمي flat-config إلى `configs.flat.recommended`؛ أما الإعداد المسبق المنفصل `recommended-latest` فهو مخصص لـ "قواعد المترجم التجريبية المتطورة"، لذا التزم بـ `recommended` هنا.

`eslint.config.js`:

```js
import reactHooks from 'eslint-plugin-React-hooks';
import tseslint from 'TypeScript-eslint';

export default tseslint.config(
  {
    files: ['src/**/*.{ts,tsx}'],
    extends: [
      tseslint.configs.recommended,
      reactHooks.configs.flat.recommended,
    ],
  },
);
```

مع تفعيل ذلك، ينجح `npx eslint src` في فحص مكون الخطوة 4 — حيث لا تطلب `exhaustive-deps` لا `theme` ولا `onConnected`. الآن قم بكسر القواعد عمداً. أدرج Effect Event كاعتمادية (dependency):

```tsx
  useEffect(() => {
    onLog();
  }, [onLog]); // effect event listed as a dependency
```

وسترد الإضافة بتحذير (القاعدة: `React-hooks/exhaustive-deps`):

```text
Functions returned from `useEffectEvent` must not be included in the
dependency array. Remove `onLog` from the list
```

هذا ليس تدقيقاً زائداً. هوية Effect Event تتغير عمداً مع كل عملية رندر — تصف الوثائق الهوية غير المستقرة بأنها "تأكيد وقت التشغيل" (runtime assertion) — لذا فإن الاعتماد عليها سيؤدي لإعادة تشغيل الـ Effect مع كل رندر.[^1] جرب استدعاء واحدة من معالج نقر (click handler) بدلاً من ذلك:

```tsx
  return <button onClick={() => onLog()}>log</button>;
```

وستحصل على خطأ جسيم (القاعدة: `React-hooks/rules-of-hooks`):

```text
`onLog` is a function created with React Hook "useEffectEvent", and can
only be called from Effects and Effect Events in the same component
```

كلتا الرسالتين أعلاه هما مخرجات ESLint حرفية من `eslint-plugin-React-hooks@7.1.1`.

## useEffectEvent مقابل useCallback: أيهما تحتاج؟

هل `useEffectEvent` بديل لـ `useCallback`؟ لا — فهما يحلان مشكلتين متضادتين. `useCallback` يمنحك *هوية مستقرة* يمكنك تمريرها؛ بينما يمنحك Effect Event *قيماً حديثة* داخل الـ Effects ولا يمكنه مغادرة المكون. الوثائق صريحة: بالنسبة لردود الفعل (callbacks) التي تمررها للأبناء أو تستدعيها من معالجات الأحداث، "استخدم دالة عادية أو `useCallback` بدلاً من ذلك."[^1]

| أنت بحاجة إلى...                                              | استخدم                                                                    |
| ------------------------------------------------------------- | ------------------------------------------------------------------------- |
| قراءة أحدث الـ props/state داخل Effect دون إعادة تشغيله       | `useEffectEvent`                                                          |
| دالة بهوية مستقرة لتمريرها إلى مكون ابن محفوظ (memoized)      | `useCallback`                                                             |
| إعادة تشغيل الـ Effect عند تغير قيمة ما (هذا منطق الـ Effect) | أبقِها في مصفوفة الاعتماديات                                              |
| رد فعل (callback) لمعالج حدث أو لمكون/Hook آخر                | دالة عادية أو `useCallback`                                               |
| إسكات `exhaustive-deps` لأن قائمة الاعتماديات "تبدو خاطئة"    | لا شيء مما سبق — أعد الهيكلة؛ إخفاء الاعتماديات يخفي الأخطاء البرمجية[^1] |

قاعدة الحدود: لا يمكن التصريح عن Effect Events إلا في نفس المكون أو الـ Hook الخاص بالـ Effect التابع لها، ولا يمكن تمريرها إلى مكونات أو Hooks أخرى، ولا يمكن استدعاؤها أثناء الرندر.[^1] وهي تعمل داخل الـ Hooks المخصصة — حيث يقوم `useInterval` في الوثائق بتغليف رد الفعل الوارد في Effect Event بحيث لا يؤدي رد الفعل الجديد في كل رندر إلى إعادة ضبط المؤقت (interval).[^1]

## التحقق

اختبار أخير يكمل المجموعة — حارس وقت الرندر. أضفه إلى `tests/feed.test.tsx`:

```tsx
function CallsDuringRender() {
  const onPing = useEffectEvent(() => {});
  onPing(); // wrong on purpose
  return null;
}

test('calling an effect event during render throws', () => {
  expect(() => render(<CallsDuringRender />)).toThrowError();
});
```

ثم قم بتشغيل مجموعة الاختبارات بالكامل:

```bash
npx tsc --noEmit && npx vitest run
```

المخرجات المتوقعة (مختصرة):

```text
 ✓ tests/feed.test.tsx (5 tests)

 Test Files  1 passed (1)
      Tests  5 passed (5)
```

خمسة اختبارات ناجحة: تقلب إعادة الاتصال في النسخة البدائية، التنبيه (toast) القديم في النسخة المكبوتة، الاتصال الفردي مع الثيم الحديث باستخدام `useEffectEvent`، إعادة الاتصال عند تغيير المصدر، وحارس وقت الرندر أعلاه. ثم `npx eslint src` لعملية فحص نظيفة.

## استكشاف الأخطاء وإصلاحها

**"A function wrapped in useEffectEvent can't be called during rendering."** — لقد استدعيت Effect Event في جسم المكون. هذا خطأ في وقت التشغيل (تم التحقق منه في `React@19.2.7`؛ اختبارنا الخامس يؤكد ذلك). انقل الاستدعاء إلى `useEffect` — أو إذا كان المنطق يجب أن يعمل أثناء الرندر، فهو ليس حدثاً (event)؛ فلا تقم بتغليفه.[^1]

**"Functions returned from `useEffectEvent` must not be included in the dependency array."** — تحذير من `React-hooks/exhaustive-deps`. قم بإزالة Effect Event من الاعتماديات؛ فهي مستبعدة حسب التصميم.[^1]

**"...can only be called from Effects and Effect Events in the same component."** — خطأ من `React-hooks/rules-of-hooks`. لقد استدعيت Effect Event من معالج حدث أو مررتها إلى مكون ابن. استخدم دالة عادية أو `useCallback` لهذه المسارات.[^1]

**يتوقف ESLint مع رسالة: A config object has a "plugins" key defined as an array of strings.** — لقد قمت بتحميل إعداد مسبق بتنسيق قديم (مثل `reactHooks.configs['recommended-latest']` في 7.1.1) في ESLint ذو الإعداد المسطح (flat-config). استخدم `reactHooks.configs.flat.recommended` كما في الخطوة 5.

**TypeScript: `Module '"React"' has no exported member 'useEffectEvent'.`** — إصدار `React` أو `@types/React` لديك أقدم من 19.2. قم بترقية كليهما (وقت تشغيل `React@19.2.x` و `@types/React@19.2.x` — يتم إصدار النسختين بشكل مستقل على npm).

## الخطوات التالية

إذا كانت أساسيات `useEffect` هي الجزء غير المستقر لديك، فابدأ بـ [دليل useEffect حول مصفوفات الاعتماديات والتنظيف](https://nerdleveltech.com/React-useeffect-2025) ثم عد إلى هذا الدرس التعليمي حول React useEffectEvent. لمزيد من ممارسات React 19 العملية، راجع [Server Actions وواجهة المستخدم المتفائلة مع React 19 في Next.js 16](https://nerdleveltech.com/nextjs-16-server-actions-React-19-optimistic-ui-tutorial)؛ كما يظهر `useEffectEvent` في [ملخص التحضير لمقابلات React لميزات 19.2](https://nerdleveltech.com/mastering-the-React-interview-top-questions-and-best-practices). من هنا، هناك توسعان طبيعيان: تغليف هذا النمط في Hook مخصص قابل لإعادة الاستخدام `useConnection(source)` (تعمل Effect Events داخل الـ Hooks المخصصة[^1])، واستكشاف ، وهي الـ API البارزة الأخرى في React 19.2.[^2]

[^5]: [Vite — البداية](https://vite.dev/guide/) — أمر الإنشاء ومتطلبات Node.js 20.19+ / 22.12+.

[^2]: [منشور إصدار React 19.2 (1 أكتوبر 2025)](https://React.dev/blog/2025/10/01/React-19-2) — `useEffectEvent`، و `<Activity />`، و `cacheSignal`، وملاحظات eslint-plugin-React-hooks v6.

[^4]: [React على npm](https://www.npmjs.com/package/React) — كان الإصدار 19.2.7 هو أحدث إصدار مستقر وقت الكتابة (نُشر في 1 يونيو 2026، وفقاً لسجل npm).

[^1]: [useEffectEvent — مرجع React الرسمي](https://React.dev/reference/React/useEffectEvent) — API، والتحذيرات، ودلالات الهوية، ومدخلات استكشاف الأخطاء وإصلاحها.

[^6]: [فصل الأحداث عن التأثيرات — React.dev](https://React.dev/learn/separating-events-from-effects) — التعمق المفاهيمي وراء أحداث التأثير (Effect Events).

[^3]: [eslint-plugin-React-hooks — مرجع القواعد الرسمي](https://React.dev/reference/eslint-plugin-React-hooks) — الإعداد المسبق الموصى به، و `exhaustive-deps`، و `rules-of-hooks`.

[^7]: [إصدار ESLint v10.0.0](https://eslint.org/blog/2026/02/eslint-v10.0.0-released/) — تمت إزالة نظام التكوين القديم `.eslintrc` في الإصدار v10؛ يتم دعم التكوين المسطح (flat config) فقط.
