logo by @sawaratsuki1004
React
v19.2
Learn
Reference
Community
Blog

Is this page useful?

On this page

  • Overview
  • الاستجابة الى الأحداث
  • الحالة: ذاكرة المكون
  • التصيير والإيداع
  • الحالة كأنها نسخة
  • جدولة سلسلة من تحديثات الحالة
  • تحديث الكائنات فى الحالة
  • تحديث المصفوفات فى الحالة
  • ماذا بعد؟

    GET STARTED

  • Quick Start
    • Tutorial: Tic-Tac-Toe
    • Thinking in React
  • Installation
    • Creating a React App
    • Build a React App from Scratch
    • Add React to an Existing Project
  • Setup
    • Editor Setup
    • Using TypeScript
    • React Developer Tools
  • React Compiler
    • Introduction
    • Installation
    • Incremental Adoption
    • Debugging and Troubleshooting
  • LEARN REACT

  • Describing the UI
    • Your First Component
    • Importing and Exporting Components
    • Writing Markup with JSX
    • JavaScript in JSX with Curly Braces
    • Passing Props to a Component
    • Conditional Rendering
    • Rendering Lists
    • Keeping Components Pure
    • Your UI as a Tree
  • Adding Interactivity
    • Responding to Events
    • State: A Component's Memory
    • Render and Commit
    • State as a Snapshot
    • Queueing a Series of State Updates
    • Updating Objects in State
    • Updating Arrays in State
  • Managing State
    • Reacting to Input with State
    • Choosing the State Structure
    • Sharing State Between Components
    • Preserving and Resetting State
    • Extracting State Logic into a Reducer
    • Passing Data Deeply with Context
    • Scaling Up with Reducer and Context
  • Escape Hatches
    • Referencing Values with Refs
    • Manipulating the DOM with Refs
    • Synchronizing with Effects
    • You Might Not Need an Effect
    • Lifecycle of Reactive Effects
    • Separating Events from Effects
    • Removing Effect Dependencies
    • Reusing Logic with Custom Hooks
Learn React

إضافة التفاعلية

بعض الاشياء على الشاشة يتم تحديثها للاستجابة لمدخلات المستخدم. على سبيل المثال، النقر على معرض صور يغير الصورة المعروضة. فى React، البيانات التي تتغير على مر الزمن تسمى حالة (state). يمكنك إضافة حالة إلى أي مكون، وتحديثها على حسب احتياجك. فى هذا الفصل، سوف تتعلم كيفية كتابة مكونات يمكنها أن تتعامل مع تفاعلات المستخدم، وتحديث حالتها، وعرض مخرجات مختلفة باختلاف المدخلات.

In this chapter

  • كيفية التعامل مع الأحداث التي يبدأها المستخدم
  • كيفية جعل المكونات تتذكر المعلومات باستخدام الحالة
  • كيفية تحديث واجهة المستخدم في React في مرحلتين
  • لماذا لا تتحدث الحالة مباشرة بعد تغييرها
  • كيفية جدولة تحديثات عديدة للحالة
  • كيفية تحديث كائن فى حالة
  • كيفية تحديث مصفوفة فى حالة

الاستجابة الى الأحداث

تتيح لك React إضافة معالجي الأحداث (event handlers) إلى JSX الخاص بك. معالجة الأحداث هم الدوال الخاصة التي سيتم تنشيطها استجابةً لتفاعلات المستخدم مثل النقر، والتمرير، والتركيز على مدخلات النموذج، وما إلى ذلك.

المكونات المدمجة مثل <button> تدعم الأحداث المتاحة فى المتصفح مثل onClick. ومع ذلك، يمكنك أيضًا إنشاء مكوناتك الخاصة وتعطي خصائص معالجة الأحداث الخاصة بها أى اسماء مخصصة للتطبيق على حسب رغبتك.

export default function App() { return ( <Toolbar onPlayMovie={() => alert('شغال!')} onUploadImage={() => alert('يتم الرفع!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> شغل الفيلم </Button> <Button onClick={onUploadImage}> ارفع صورة </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }

Ready to learn this topic?

اقرأ الاستجابة إلى الأحداث لتعلم كيفية إضافة معالجى الأحداث.

Read More

الحالة: ذاكرة المكون

غالبًا تحتاج المكونات إلى تغيير محتوى الشاشة نتيجةً لتفاعل المستخدم. مثلا، الكتابة فى نموذج يجب أن يحدث حقل الإدخال، والنقر على “التالي” على دائرة الصور يجب ان يغير الصورة المعروضة، والضغط على “شراء” يضع المنتج فى سلة المشتريات، وهكذا. المكونات تحتاج أن تتذكر اشياء مثل: القيمة الحالية لحقل الإدخال، الصورة الحالية، سلة المشتريات. فى React، هذا النوع من الذاكرة الخاصة بالمكون يسمى “حالة”

يمكنك إضافة حالة إلى مكون عن طريق استخدام خطاف useState. الخطاطيف (hooks) هي دوال خاصة تتيح لك أن تستخدم مميزات React (الحالة واحدة من هذه المميزات). خطاف useState يتيح لك أن تعرف متغير حالة. هذا الخطاف يأخذ الحالة المبدأية ويرجع قيمتين: الحالة الحالية، ودالة معين الحالة التي تتيح لك أن تغير الحالة.

const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false);

هنا كيف يقوم معرض صور تحديض الحالة (state) عند النقر:

import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); const hasNext = index < sculptureList.length - 1; function handleNextClick() { if (hasNext) { setIndex(index + 1); } else { setIndex(0); } } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; return ( <> <button onClick={handleNextClick}> التالى </button> <h2> <i>{sculpture.name} </i> بواسطة {sculpture.artist} </h2> <h3> ({index + 1} من {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'إخفاء' : 'إظهار'} التفاصيل </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }

Ready to learn this topic?

اقرأ الحالة: ذاكرة المكون لتتعلم كيفية حفظ القيم وتحديثها عند التفاعل.

Read More

التصيير والإيداع

قبل أن يتم عرض مكوناتك على الشاشة، يجب أن تُصير بواسطة React. سيساعدك فهم الخطوات في هذه العملية على التفكير في كيفية تنفيذ التعليمات البرمجية الخاصة بك وشرح سلوكها.

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

  1. تحفيز المُصير (توصيل طلب العشاء الى المطبخ)
  2. تصيير المكون (تحضير الطلب فى المطبخ)
  3. إيداع إلى الـ DOM (وضع الطلب على الطاولة)
  1. React كما لو كانت نادل فى مطعم يقوم باخذ الطلبات من المستخدمين ويقوم باحضارها الى مطبخ المكونات
    تحفيز
  2. طاهى البطاقة يعطى React مكون بطاقة جديد
    تصيير
  3. React توصل البطاقة الى المستخدم على الطاولة
    أيداع

Illustrated by Rachel Lee Nabors

Ready to learn this topic?

اقرأ تصيير وإيداع لتتعلم دورة حياة تحديثات واجهة المستخدم

Read More

الحالة كأنها نسخة

على عكس متغيرات JavaScript العادية، تتصرف حالة React مثل نسخة. لا يؤدي تعيينها إلى تغيير متغير الحالة الذي لديك بالفعل، ولكنه يؤدي بدلاً من ذلك إلى إعادة تصيير. قد يكون هذا مفاجئًا في البداية!

console.log(count); // 0 setCount(count + 1); // Request a re-render with 1 console.log(count); // Still 0!

يساعدك هذا السلوك على تجنب الثغرات. هنا تطبيق دردشة صغير. حاول تخمين ما يحدث إذا ضغطت على “إرسال” أولاً ثم غير المستلم إلى بوب. من سيظهر اسمه في “التنبيه” بعد خمس ثوانٍ؟

import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('أليس'); const [message, setMessage] = useState('مرحبا'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`انت قولت ${message} إلى ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> إلى:{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="أليس">أليس</option> <option value="بوب">بوب</option> </select> </label> <textarea placeholder="رسالة" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">إرسال</button> </form> ); }

Ready to learn this topic?

اقرأ الحالة كأنها نسخة لتتعلم لماذا تظهر الحالة وكأنها ثابتة و لا تتغير داخل معالجي الأحداث.

Read More

جدولة سلسلة من تحديثات الحالة

هذا المكون فيه ثغرة: النقر على “+3” يزيد النتيجة مرة واحدة فقط.

import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(score + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>النتيجة: {score}</h1> </> ) }

الحالة كأنها لقطة يشرح لماذا يحدث ذلك. تعيين الحالة يطلب إعادة تصيير جديدة، لكن لا يغير القيمة فى الكود الذى يعمل بالفعل. لذلك تظل النتيجة “1” بعد استدعاء “setScore(score + 1)“.

console.log(score); // 0 setScore(score + 1); // setScore(0 + 1); console.log(score); // 0 setScore(score + 1); // setScore(0 + 1); console.log(score); // 0 setScore(score + 1); // setScore(0 + 1); console.log(score); // 0

يمكنك ان تصلح ذلك عن طريق تمرير دالة تحديث عند تعين الحالة. لاحظ انه عند تغير setScore(score + 1) بـ setScore(s => s + 1) يصلح زرار الـ “+3”. هذا يمنحك أن تجدول أكثر من تحديث حالة.

import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(s => s + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>النتيجة: {score}</h1> </> ) }

Ready to learn this topic?

اقرأ جدولة سلسلة من تحديثات الحالة لتتعلم كيفية جدولة سلسلة من تحديثات الحالة.

Read More

تحديث الكائنات فى الحالة

يمكن ان تحتوى الحالة على أى نوع من قيم JavaScript، يشمل ذلك الكائنات. لكن يجب أن لا تغير الكائنات والمصفوفات التى تحتوى عليها حالة React مباشراً. بدلاً من ذلك عندما تحدث كائن أو مصفوفة، تحتاج أن تنشئ نسخة جديدة (أو تقوم بعمل نسخة من نسخة موجودة)، بعد ذلك حدث الحالة باستخدام النسخة.

فى الغالب، سوف تستخدم معامل البسط (spread operator) ... لعمل نسخة من الكائنات والمصفوفات التى تريد أن تغيرها. على سبيل المثال، تحديث كائن متداخل يمكن أن تكون مثل ذلك:

import { useState } from 'react'; export default function Form() { const [person, setPerson] = useState({ name: 'نيكي دي سانت فال', artwork: { title: 'بلو نانا', city: 'هامبورغ', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { setPerson({ ...person, name: e.target.value }); } function handleTitleChange(e) { setPerson({ ...person, artwork: { ...person.artwork, title: e.target.value } }); } function handleCityChange(e) { setPerson({ ...person, artwork: { ...person.artwork, city: e.target.value } }); } function handleImageChange(e) { setPerson({ ...person, artwork: { ...person.artwork, image: e.target.value } }); } return ( <> <label> الاسم: <input value={person.name} onChange={handleNameChange} /> </label> <label> اسم اللوحة: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> المدينة: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> الصورة: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' من '} {person.name} <br /> (موجودة فى {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }

لو وجدت نسخ الكائنات فى الكود مملًا، يمكنك استخدام مكتبة مثل Immer لتقليل الكود المتكرر:

import { useImmer } from 'use-immer'; export default function Form() { const [person, updatePerson] = useImmer({ name: 'نيكي دي سانت فال', artwork: { title: 'بلو نانا', city: 'هامبورغ', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { updatePerson(draft => { draft.name = e.target.value; }); } function handleTitleChange(e) { updatePerson(draft => { draft.artwork.title = e.target.value; }); } function handleCityChange(e) { updatePerson(draft => { draft.artwork.city = e.target.value; }); } function handleImageChange(e) { updatePerson(draft => { draft.artwork.image = e.target.value; }); } return ( <> <label> الأسم: <input value={person.name} onChange={handleNameChange} /> </label> <label> اسم اللوحة: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> المدينة: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> الصورة: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' من '} {person.name} <br /> (موجودة فى {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }

Ready to learn this topic?

اقرأ تحديث الكائنات فى الحالة لتتعلم كيفية تحديث الكائنات بطريقة صحيحة

Read More

تحديث المصفوفات فى الحالة

المصفوفات هي نوع آخر من كائنات JavaScript القابلة للتغيير والتي يمكنك تخزينها في الحالة ويجب التعامل معها على أنها للقراءة فقط. تمامًا كما هو الحال مع الكائنات، عندما تريد تحديث مصفوفة مخزنة في الحالة، فأنت بحاجة إلى إنشاء مصفوفة جديدة (أو إنشاء نسخة من واحدة موجودة)، ثم تعيين الحالة لاستخدام المصفوفة الجديدة:

import { useState } from 'react'; const initialList = [ { id: 0, title: 'بطون كبيرة', seen: false }, { id: 1, title: 'منظر القمر', seen: false }, { id: 2, title: 'جيش الطين', seen: true }, ]; export default function BucketList() { const [list, setList] = useState( initialList ); function handleToggle(artworkId, nextSeen) { setList(list.map(artwork => { if (artwork.id === artworkId) { return { ...artwork, seen: nextSeen }; } else { return artwork; } })); } return ( <> <h1>قائمة امنيات الفن</h1> <h2>قائمة الفن الذى اريد ان أراه:</h2> <ItemList artworks={list} onToggle={handleToggle} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }

لو وجدت نسخ المصفوفات فى الكود مملًا، يمكنك استخدام مكتبة مثل Immer لتقليل الكود المتكرر:

import { useState } from 'react'; import { useImmer } from 'use-immer'; const initialList = [ { id: 0, title: 'بطون كبيرة', seen: false }, { id: 1, title: 'منظر القمر', seen: false }, { id: 2, title: 'جيش الطين', seen: true }, ]; export default function BucketList() { const [list, updateList] = useImmer(initialList); function handleToggle(artworkId, nextSeen) { updateList(draft => { const artwork = draft.find(a => a.id === artworkId ); artwork.seen = nextSeen; }); } return ( <> <h1>قائمة امنيات الفن</h1> <h2>قائمة الفن الذى اريد ان أراه:</h2> <ItemList artworks={list} onToggle={handleToggle} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }

Ready to learn this topic?

اقرأ تحديث المصفوفات فى الحالة لتتعلم كيفية تحديث المصفوفات بطريقة صحيحة.

Read More

ماذا بعد؟

إذهب الى الإستجابة الى الاحداث لتقرأ هذا الفصل صفحة بصفحة!

أو، إذا كنت تفهم هذه المواضيع بالفعل، لماذا لا تقرأ عن إدارة الحالة؟

PreviousYour UI as a Tree
NextResponding to Events

Copyright © Meta Platforms, Inc
no uwu plz
uwu?
Logo by@sawaratsuki1004
Learn React
Quick Start
Installation
Describing the UI
Adding Interactivity
Managing State
Escape Hatches
API Reference
React APIs
React DOM APIs
Community
Code of Conduct
Meet the Team
Docs Contributors
Acknowledgements
More
Blog
React Native
Privacy
Terms
Fork
export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('شغال!')}
      onUploadImage={() => alert('يتم الرفع!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        شغل الفيلم
      </Button>
      <Button onClick={onUploadImage}>
        ارفع صورة
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
Fork
import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        التالى
      </button>
      <h2>
        <i>{sculpture.name} </i>
        بواسطة {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} من {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'إخفاء' : 'إظهار'} التفاصيل
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
Fork
import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('أليس');
  const [message, setMessage] = useState('مرحبا');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`انت قولت ${message} إلى ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        إلى:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="أليس">أليس</option>
          <option value="بوب">بوب</option>
        </select>
      </label>
      <textarea
        placeholder="رسالة"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">إرسال</button>
    </form>
  );
}

Fork
import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>النتيجة: {score}</h1>
    </>
  )
}

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
Fork
import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>النتيجة: {score}</h1>
    </>
  )
}

Fork
import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'نيكي دي سانت فال',
    artwork: {
      title: 'بلو نانا',
      city: 'هامبورغ',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        الاسم:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        اسم اللوحة:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        المدينة:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        الصورة:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' من '}
        {person.name}
        <br />
        (موجودة فى {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Fork
{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}
Fork
import { useState } from 'react';

const initialList = [
  { id: 0, title: 'بطون كبيرة', seen: false },
  { id: 1, title: 'منظر القمر', seen: false },
  { id: 2, title: 'جيش الطين', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>قائمة امنيات الفن</h1>
      <h2>قائمة الفن الذى اريد ان أراه:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Fork
{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}