useState

useState це React-хук, який дозволяє додати змінну стану до вашого компонента.

const [state, setState] = useState(initialState)

Довідник

useState(initialState)

Викличте useState на верхньому рівні вашого компонента, щоб оголосити змінну стану .

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
  // ...

Угода полягає у тому, щоб називати змінні стану на зразок [something, setSomething] з використанням деструктуризації масивів.

Дивіться більше прикладів нижче.

Параметри

  • initialState: Значення, яким ви хочете бачити стан на початку. Це може бути значення будь-якого типу, але для функцій існує спеціальна поведінка. Цей аргумент ігнорується після початкового рендерингу.
    • Якщо ви передасте функцію як initialState, вона буде розглядатися як функція-ініціалізатор. Вона має бути чистою, не приймати жодних аргументів і повертати значення будь-якого типу. React викличе вашу функцію ініціалізатора під час ініціалізації компонента і збереже значення, яке вона повертає, як початковий стан. Дивіться приклад нижче.

Повернення

useState повертає масив, що містить рівно два значення:

  1. Поточний стан. Під час першого рендерингу він відповідатиме initialState, який ви передали.
  2. Функція set, яка дозволяє оновити стан до іншого значення і викликати повторний рендеринг.

Застереження


встановити функції, такі як setSomething(nextState)

Функція set, яку повертає useState, дозволяє оновити стан до іншого значення і запустити повторний рендеринг. Ви можете передати наступний стан безпосередньо або функцію, яка обчислює його з попереднього стану:

const [name, setName] = useState('Edward');

function handleClick() {
  setName('Taylor');
  setAge(a => a + 1);
  // ...

Параметри

  • nextState: Значення, яким має бути стан. Це може бути значення будь-якого типу, але для функцій існує спеціальна поведінка.
    • Якщо ви передасте функцію як nextState, вона буде оброблена як функція оновлення. Вона має бути чистою, приймати стан очікування як єдиний аргумент і повертати наступний стан. React поставить вашу функцію оновлення в чергу і перезавантажить ваш компонент. Під час наступного рендерингу React обчислить наступний стан, застосувавши всі оновлювачі з черги до попереднього стану. Дивіться приклад нижче.

Повернення

функції set не мають значення, що повертається.

Застереження

  • Функція set лише оновлює змінну стану для наступного рендерингу . Якщо ви прочитаєте змінну стану після виклику функції set, ви все одно отримаєте старе значення, яке було на екрані до вашого виклику.

  • Якщо нове значення, яке ви надали, ідентичне поточному стану, як визначено порівнянням Object.is, React пропустить повторний рендеринг компонента та його дочірніх елементів. Це оптимізація. Хоча в деяких випадках React все ще може викликати ваш компонент перед тим, як пропустити дочірні, це не повинно вплинути на ваш код.

  • React пакетно оновлює стан. Він оновлює екран після запуску всіх обробників подій та виклику їхніх функцій set. Це запобігає багаторазовому повторному рендерингу під час однієї події. У рідкісних випадках, коли вам потрібно змусити React оновити екран раніше, наприклад, для доступу до DOM, ви можете використовувати flushSync.

  • Виклик функції set під час рендерингу дозволено лише з поточного компонента. React відхидить її виведення і негайно спробує відрендерити її знову з новим станом. Цей патерн рідко потрібен, але ви можете використовувати його для збереження інформації з попередніх рендерингів. Дивіться приклад нижче.

  • У суворому режимі React викличе вашу функцію оновлення двічі, щоб допомогти вам знайти випадкові домішки. Це поведінка лише для розробки і не впливає на виробництво. Якщо ваша функція оновлення є чистою (як і має бути), це не повинно вплинути на поведінку. Результат одного з викликів буде проігноровано.


Використання

Додавання стану до компонента

Викличте useState на верхньому рівні вашого компонента, щоб оголосити одну або декілька змінних стану.

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(42);
  const [name, setName] = useState('Taylor');
  // ...

Угода полягає у тому, щоб називати змінні стану на зразок [something, setSomething] з використанням деструктуризації масивів.

useState повертає масив, що містить рівно два елементи:

  1. Поточний стан Крок коду</CodeStep> цієї змінної стану, початково встановленої у <CodeStep data-step="3">початковий стан</CodeStep> ви надали.</li> <li>Функція <CodeStep data-step="2"><code>set що дозволяє змінити його на будь-яке інше значення у відповідь на взаємодію.

Щоб оновити те, що на екрані, викличте функцію set з деяким наступним станом:

function handleClick() {
  setName('Robin');
}

React збереже наступний стан, відрендерить ваш компонент знову з новими значеннями та оновить інтерфейс користувача.

Виклик функції set не змінює поточний стан у вже виконуваному коді :

function handleClick() {
  setName('Robin');
  console.log(name); // Still "Taylor"!
}

Це впливає лише на те, що useState повертатиме, починаючи з наступного рендерингу.

Лічильник (число)

У цьому прикладі змінна стану count містить число. Натискання кнопки збільшує його.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      You pressed me {count} times
    </button>
  );
}

Текстове поле (рядок)

У цьому прикладі змінна стану text містить рядок. Коли ви вводите текст, handleChange зчитує останнє вхідне значення з DOM-елемента введення браузера і викликає setText для оновлення стану. Це дозволяє відобразити поточний текст нижче.

import { useState } from 'react';

export default function MyInput() {
  const [text, setText] = useState('hello');

  function handleChange(e) {
    setText(e.target.value);
  }

  return (
    <>
      <input value={text} onChange={handleChange} />
      <p>You typed: {text}</p>
      <button onClick={() => setText('hello')}>
        Reset
      </button>
    </>
  );
}

Прапорець (boolean)

У цьому прикладі змінна стану liked містить логічне значення. Коли ви натискаєте на вхід, setLiked оновлює змінну стану liked тим, чи встановлено прапорець у вікні браузера. Змінна liked використовується для відображення тексту під прапорцем.

import { useState } from 'react';

export default function MyCheckbox() {
  const [liked, setLiked] = useState(true);

  function handleChange(e) {
    setLiked(e.target.checked);
  }

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={liked}
          onChange={handleChange}
        />
        I liked this
      </label>
      <p>You {liked ? 'liked' : 'did not like'} this.</p>
    </>
  );
}

Форма (дві змінні)

В одному компоненті можна оголосити більше однієї змінної стану. Кожна змінна стану є повністю незалежною.

import { useState } from 'react';

export default function Form() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);

  return (
    <>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <button onClick={() => setAge(age + 1)}>
        Increment age
      </button>
      <p>Hello, {name}. You are {age}.</p>
    </>
  );
}
button { display: block; margin-top: 10px; }

Оновлення стану на основі попереднього стану

Припустимо, що age дорівнює 42. Цей обробник викликає setAge(age + 1) тричі:

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

Проте після одного клацання миші вік стане лише 43, а не 45! Це відбувається тому, що виклик функції set не оновлює змінну стану age у вже запущеному коді. Тому кожен виклик setAge(age + 1) стає setAge(43).

Щоб вирішити цю проблему, ви можете передати функцію оновлення setAge замість наступного стану:

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

Тут a => a + 1 - ваша функція оновлення. Вона приймає стан очікування </CodeStep> та обчислює <CodeStep data-step="2">наступний стан</CodeStep> з нього.</p> <p>React поміщає ваші функції оновлення в <a href="/learn/queueing-a-series-of-state-updates">чергу.</a> Потім, під час наступного рендеру, він буде викликати їх у тому ж порядку:</p> <ol><li><code>a => a + 1 отримає 42 як стан очікування і поверне 43 як наступний стан.

  • a => a + 1 отримає 43 як стан очікування і поверне 44 як наступний стан.
  • a => a + 1 отримає 44 як стан очікування і поверне 45 як наступний стан.
  • Немає інших оновлень у черзі, тому React збереже 45 як поточний стан в кінці.

    Зазвичай прийнято називати аргумент очікуваного стану за першою літерою назви змінної стану, наприклад a для age. Втім, ви також можете назвати її як prevAge або якось інакше, як вам зручніше.

    React може двічі викликати ваші оновлювачі під час розробки, щоб перевірити, чи вони чисті.

    Чи завжди слід використовувати програму оновлення?

    Ви можете почути рекомендацію завжди писати код на зразок setAge(a => a + 1), якщо стан, який ви встановлюєте, обчислюється на основі попереднього стану. У цьому немає нічого поганого, але це також не завжди необхідно.

    У більшості випадків між цими двома підходами немає різниці. React завжди гарантує, що для навмисних дій користувача, таких як кліки, змінна стану age буде оновлена перед наступним кліком. Це означає, що немає ризику того, що обробник кліку побачить "застаріле" значення age на початку обробника події.

    Втім, якщо ви виконуєте кілька оновлень у межах однієї події, вам можуть стати у пригоді оновлювачі. Вони також стануть у пригоді, якщо доступ до самої змінної стану є незручним (ви можете зіткнутися з цим під час оптимізації повторного рендерингу).

    Якщо ви надаєте перевагу узгодженості, а не трохи більш багатослівному синтаксису, розумно завжди писати оновлювач, якщо стан, який ви встановлюєте, обчислюється від попереднього стану. Якщо стан обчислюється на основі попереднього стану деякої іншої змінної стану, ви можете об'єднати їх в один об'єкт і використовувати редуктор.

    Передача функції оновлення

    У цьому прикладі передано функцію оновлення, тому кнопка "+3" працює.

    import { useState } from 'react';
    
    export default function Counter() {
      const [age, setAge] = useState(42);
    
      function increment() {
        setAge(a => a + 1);
      }
    
      return (
        <>
          <h1>Your age: {age}</h1>
          <button onClick={() => {
            increment();
            increment();
            increment();
          }}>+3</button>
          <button onClick={() => {
            increment();
          }}>+1</button>
        </>
      );
    }
    button { display: block; margin: 10px; font-size: 20px; }
    h1 { display: block; margin: 10px; }

    Перехід до наступного стану безпосередньо

    Цей приклад не передає функцію оновлення, тому кнопка "+3" не працює належним чином.

    import { useState } from 'react';
    
    export default function Counter() {
      const [age, setAge] = useState(42);
    
      function increment() {
        setAge(age + 1);
      }
    
      return (
        <>
          <h1>Your age: {age}</h1>
          <button onClick={() => {
            increment();
            increment();
            increment();
          }}>+3</button>
          <button onClick={() => {
            increment();
          }}>+1</button>
        </>
      );
    }
    button { display: block; margin: 10px; font-size: 20px; }
    h1 { display: block; margin: 10px; }

    Оновлення об'єктів та масивів у стані

    Ви можете переводити об'єкти та масиви у стан. У React стан вважається доступним лише для читання, тому вам слід замінити його, а не мутувати ваші існуючі об'єкти. Наприклад, якщо у вас є об'єкт form у стані, не мутуйте його:

    // 🚩 Don't mutate an object in state like this:
    form.firstName = 'Taylor';

    Натомість замініть весь об'єкт, створивши новий:

    // ✅ Replace state with a new object
    setForm({
      ...form,
      firstName: 'Taylor'
    });

    Прочитайте Оновлення об'єктів у стані та Оновлення масивів у стані щоб дізнатися більше.

    Форма (об'єкт)

    У цьому прикладі змінна стану форми містить об'єкт. Кожне введення має обробник змін, який викликає setForm з наступним станом всієї форми. Синтаксис поширення { ...form } гарантує, що об'єкт стану буде замінено, а не мутовано.

    import { useState } from 'react';
    
    export default function Form() {
      const [form, setForm] = useState({
        firstName: 'Barbara',
        lastName: 'Hepworth',
        email: '[email protected]',
      });
    
      return (
        <>
          <label>
            First name:
            <input
              value={form.firstName}
              onChange={e => {
                setForm({
                  ...form,
                  firstName: e.target.value
                });
              }}
            />
          </label>
          <label>
            Last name:
            <input
              value={form.lastName}
              onChange={e => {
                setForm({
                  ...form,
                  lastName: e.target.value
                });
              }}
            />
          </label>
          <label>
            Email:
            <input
              value={form.email}
              onChange={e => {
                setForm({
                  ...form,
                  email: e.target.value
                });
              }}
            />
          </label>
          <p>
            {form.firstName}{' '}
            {form.lastName}{' '}
            ({form.email})
          </p>
        </>
      );
    }
    label { display: block; }
    input { margin-left: 5px; }

    Форма (вкладений об'єкт)

    У цьому прикладі стан є більш вкладеним. Коли ви оновлюєте вкладений стан, вам потрібно створити копію об'єкта, який ви оновлюєте, а також всіх об'єктів, що "містять" його на шляху вгору. Прочитайте оновлення вкладеного об'єкта, щоб дізнатися більше.

    import { useState } from 'react';
    
    export default function Form() {
      const [person, setPerson] = useState({
        name: 'Niki de Saint Phalle',
        artwork: {
          title: 'Blue Nana',
          city: 'Hamburg',
          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>
            Name:
            <input
              value={person.name}
              onChange={handleNameChange}
            />
          </label>
          <label>
            Title:
            <input
              value={person.artwork.title}
              onChange={handleTitleChange}
            />
          </label>
          <label>
            City:
            <input
              value={person.artwork.city}
              onChange={handleCityChange}
            />
          </label>
          <label>
            Image:
            <input
              value={person.artwork.image}
              onChange={handleImageChange}
            />
          </label>
          <p>
            <i>{person.artwork.title}</i>
            {' by '}
            {person.name}
            <br />
            (located in {person.artwork.city})
          </p>
          <img 
            src={person.artwork.image} 
            alt={person.artwork.title}
          />
        </>
      );
    }
    label { display: block; }
    input { margin-left: 5px; margin-bottom: 5px; }
    img { width: 200px; height: 200px; }

    Список (масив)

    У цьому прикладі змінна стану todos містить масив. Кожен обробник кнопки викликає setTodos з наступною версією цього масиву. Розширений синтаксис [...todos], todos.map() та todos.filter() забезпечує заміну масиву стану, а не його мутацію.

    import { useState } from 'react';
    import AddTodo from './AddTodo.js';
    import TaskList from './TaskList.js';
    
    let nextId = 3;
    const initialTodos = [
      { id: 0, title: 'Buy milk', done: true },
      { id: 1, title: 'Eat tacos', done: false },
      { id: 2, title: 'Brew tea', done: false },
    ];
    
    export default function TaskApp() {
      const [todos, setTodos] = useState(initialTodos);
    
      function handleAddTodo(title) {
        setTodos([
          ...todos,
          {
            id: nextId++,
            title: title,
            done: false
          }
        ]);
      }
    
      function handleChangeTodo(nextTodo) {
        setTodos(todos.map(t => {
          if (t.id === nextTodo.id) {
            return nextTodo;
          } else {
            return t;
          }
        }));
      }
    
      function handleDeleteTodo(todoId) {
        setTodos(
          todos.filter(t => t.id !== todoId)
        );
      }
    
      return (
        <>
          <AddTodo
            onAddTodo={handleAddTodo}
          />
          <TaskList
            todos={todos}
            onChangeTodo={handleChangeTodo}
            onDeleteTodo={handleDeleteTodo}
          />
        </>
      );
    }
    import { useState } from 'react';
    
    export default function AddTodo({ onAddTodo }) {
      const [title, setTitle] = useState('');
      return (
        <>
          <input
            placeholder="Add todo"
            value={title}
            onChange={e => setTitle(e.target.value)}
          />
          <button onClick={() => {
            setTitle('');
            onAddTodo(title);
          }}>Add</button>
        </>
      )
    }
    import { useState } from 'react';
    
    export default function TaskList({
      todos,
      onChangeTodo,
      onDeleteTodo
    }) {
      return (
        <ul>
          {todos.map(todo => (
            <li key={todo.id}>
              <Task
                todo={todo}
                onChange={onChangeTodo}
                onDelete={onDeleteTodo}
              />
            </li>
          ))}
        </ul>
      );
    }
    
    function Task({ todo, onChange, onDelete }) {
      const [isEditing, setIsEditing] = useState(false);
      let todoContent;
      if (isEditing) {
        todoContent = (
          <>
            <input
              value={todo.title}
              onChange={e => {
                onChange({
                  ...todo,
                  title: e.target.value
                });
              }} />
            <button onClick={() => setIsEditing(false)}>
              Save
            </button>
          </>
        );
      } else {
        todoContent = (
          <>
            {todo.title}
            <button onClick={() => setIsEditing(true)}>
              Edit
            </button>
          </>
        );
      }
      return (
        <label>
          <input
            type="checkbox"
            checked={todo.done}
            onChange={e => {
              onChange({
                ...todo,
                done: e.target.checked
              });
            }}
          />
          {todoContent}
          <button onClick={() => onDelete(todo.id)}>
            Delete
          </button>
        </label>
      );
    }
    button { margin: 5px; }
    li { list-style-type: none; }
    ul, li { margin: 0; padding: 0; }

    Написання стислої логіки оновлення за допомогою Immer

    Якщо оновлення масивів та об'єктів без мутації здається вам нудним, ви можете скористатися бібліотекою на кшталт Immer для зменшення повторюваного коду. Immer дозволяє писати стислий код так, ніби ви мутуєте об'єкти, але за лаштунками вона виконує незмінні оновлення:

    import { useState } from 'react';
    import { useImmer } from 'use-immer';
    
    let nextId = 3;
    const initialList = [
      { id: 0, title: 'Big Bellies', seen: false },
      { id: 1, title: 'Lunar Landscape', seen: false },
      { id: 2, title: 'Terracotta Army', 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>Art Bucket List</h1>
          <h2>My list of art to see:</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>
      );
    }
    {
      "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"
      }
    }

    Уникнення відтворення початкового стану

    .

    React зберігає початковий стан один раз і ігнорує його при наступних рендерах.

    function TodoList() {
      const [todos, setTodos] = useState(createInitialTodos());
      // ...

    Хоча результат createInitialTodos() використовується лише для початкового рендерингу, ви все одно викликаєте цю функцію для кожного рендерингу. Це може бути марнотратством, якщо створюються великі масиви або виконуються складні обчислення.

    Щоб вирішити цю проблему, ви можете передати його як функцію-ініціалізатор до useState замість:

    function TodoList() {
      const [todos, setTodos] = useState(createInitialTodos);
      // ...

    Зверніть увагу, що ви передаєте createInitialTodos, яка є самою функцією , а не createInitialTodos(), яка є результатом її виклику. Якщо ви передасте функцію в useState, React викличе її лише під час ініціалізації.

    React може викликати ваші ініціалізатори двічі під час розробки, щоб перевірити, що вони чисті.

    Передача функції ініціалізатора

    Цей приклад передає функцію ініціалізатора, тому функція createInitialTodos працює лише під час ініціалізації. Вона не виконується під час повторного рендерингу компонента, наприклад, коли ви вводите дані у вікно введення.

    import { useState } from 'react';
    
    function createInitialTodos() {
      const initialTodos = [];
      for (let i = 0; i < 50; i++) {
        initialTodos.push({
          id: i,
          text: 'Item ' + (i + 1)
        });
      }
      return initialTodos;
    }
    
    export default function TodoList() {
      const [todos, setTodos] = useState(createInitialTodos);
      const [text, setText] = useState('');
    
      return (
        <>
          <input
            value={text}
            onChange={e => setText(e.target.value)}
          />
          <button onClick={() => {
            setText('');
            setTodos([{
              id: todos.length,
              text: text
            }, ...todos]);
          }}>Add</button>
          <ul>
            {todos.map(item => (
              <li key={item.id}>
                {item.text}
              </li>
            ))}
          </ul>
        </>
      );
    }

    Перехід до початкового стану безпосередньо

    У цьому прикладі не передається функція ініціалізатора, тому функція createInitialTodos виконується під час кожного рендерингу, наприклад, коли ви вводите дані. Помітної різниці у поведінці не спостерігається, але такий код менш ефективний.

    import { useState } from 'react';
    
    function createInitialTodos() {
      const initialTodos = [];
      for (let i = 0; i < 50; i++) {
        initialTodos.push({
          id: i,
          text: 'Item ' + (i + 1)
        });
      }
      return initialTodos;
    }
    
    export default function TodoList() {
      const [todos, setTodos] = useState(createInitialTodos());
      const [text, setText] = useState('');
    
      return (
        <>
          <input
            value={text}
            onChange={e => setText(e.target.value)}
          />
          <button onClick={() => {
            setText('');
            setTodos([{
              id: todos.length,
              text: text
            }, ...todos]);
          }}>Add</button>
          <ul>
            {todos.map(item => (
              <li key={item.id}>
                {item.text}
              </li>
            ))}
          </ul>
        </>
      );
    }

    Скидання стану за допомогою ключа

    Атрибут key часто зустрічається під час відображення списків. Однак він має й інше призначення.

    Ви можете скинути стан компонента, передавши інший ключ компоненту. У цьому прикладі кнопка Reset змінює змінну стану version, яку ми передаємо як key до Form. Коли key змінюється, React перестворює компонент Form (і всі його дочірні елементи) з нуля, тому його стан скидається.

    Прочитайте Збереження та скидання стану, щоб дізнатися більше.

    import { useState } from 'react';
    
    export default function App() {
      const [version, setVersion] = useState(0);
    
      function handleReset() {
        setVersion(version + 1);
      }
    
      return (
        <>
          <button onClick={handleReset}>Reset</button>
          <Form key={version} />
        </>
      );
    }
    
    function Form() {
      const [name, setName] = useState('Taylor');
    
      return (
        <>
          <input
            value={name}
            onChange={e => setName(e.target.value)}
          />
          <p>Hello, {name}.</p>
        </>
      );
    }
    button { display: block; margin-bottom: 20px; }

    Зберігання інформації з попередніх рендерів

    Зазвичай ви оновлюєте стан в обробниках подій. Однак у рідкісних випадках вам може знадобитися змінити стан у відповідь на рендеринг - наприклад, ви можете змінити змінну стану, коли змінюється проп.

    У більшості випадків вам це не потрібно:

    У рідкісному випадку, коли жоден з цих способів не підходить, існує шаблон, який можна використати для оновлення стану на основі значень, які вже було відрендерено, викликавши функцію set під час рендерингу вашого компонента.

    Ось приклад. Цей компонент CountLabel відображає переданий йому проп count:

    export default function CountLabel({ count }) {
      return <h1>{count}</h1>
    }

    Скажімо, ви хочете показати, чи збільшився лічильник або зменшився з часу останньої зміни. Проп count цього не показує - вам потрібно відстежувати його попереднє значення. Додайте змінну стану prevCount, щоб відстежувати її. Додайте ще одну змінну стану з назвою trend, щоб відслідковувати, чи підрахунок збільшився або зменшився. Порівняйте prevCount з count, і якщо вони не рівні, оновіть і prevCount, і trend. Тепер ви можете показати як поточний проп count, так і як він змінився з часу останнього рендерингу.

    import { useState } from 'react';
    import CountLabel from './CountLabel.js';
    
    export default function App() {
      const [count, setCount] = useState(0);
      return (
        <>
          <button onClick={() => setCount(count + 1)}>
            Increment
          </button>
          <button onClick={() => setCount(count - 1)}>
            Decrement
          </button>
          <CountLabel count={count} />
        </>
      );
    }
    import { useState } from 'react';
    
    export default function CountLabel({ count }) {
      const [prevCount, setPrevCount] = useState(count);
      const [trend, setTrend] = useState(null);
      if (prevCount !== count) {
        setPrevCount(count);
        setTrend(count > prevCount ? 'increasing' : 'decreasing');
      }
      return (
        <>
          <h1>{count}</h1>
          {trend && <p>The count is {trend}</p>}
        </>
      );
    }
    button { margin-bottom: 10px; }

    Зверніть увагу, що якщо ви викликаєте функцію set під час рендерингу, вона має бути всередині умови на кшталт prevCount !== count, а всередині умови має бути виклик типу setPrevCount(count). Інакше ваш компонент буде повторно рендеритися у циклі, доки не зазнає невдачі. Крім того, таким чином ви можете оновити стан лише компонента , що рендериться у даний момент . Виклик функції set компонента іншого під час рендерингу є помилкою. Нарешті, ваш виклик set все одно повинен оновлювати стан без мутації - це не означає, що ви можете порушувати інші правила чистих функцій.

    Цей шаблон може бути важко зрозуміти, і його зазвичай краще уникати. Однак, це краще, ніж оновлювати стан в ефекті. Коли ви викликаєте функцію set під час рендерингу, React повторно відрендерить цей компонент одразу після того, як ваш компонент вийде з оператором return, і перед рендерингом дочірніх. Таким чином, дочірні компоненти не потрібно рендерити двічі. Решта функцій вашого компонента все одно буде виконана (і результат буде відкинуто). Якщо ваша умова нижче всіх викликів хука, ви можете додати ранній return;, щоб перезапустити рендеринг раніше.


    Налагодження

    Я оновив стан, але журнал показує старе значення

    Виклик функції set не змінює стан у коді, що виконується:

    function handleClick() {
      console.log(count);  // 0
    
      setCount(count + 1); // Request a re-render with 1
      console.log(count);  // Still 0!
    
      setTimeout(() => {
        console.log(count); // Also 0!
      }, 5000);
    }

    Це пов'язано з тим, що стани поводяться як знімок. Оновлення стану вимагає нового рендерингу з новим значенням стану, але не впливає на countзмінну JavaScript у вашому вже запущеному обробнику події.

    Якщо вам потрібно використати наступний стан, ви можете зберегти його у змінній перед передачею у функцію set:

    const nextCount = count + 1;
    setCount(nextCount);
    
    console.log(count);     // 0
    console.log(nextCount); // 1

    Я оновив стан, але екран не оновлюється

    React проігнорує ваше оновлення, якщо наступний стан дорівнює попередньому, як визначено порівнянням Object.is. Зазвичай це трапляється, коли ви безпосередньо змінюєте об'єкт або масив у стані:

    obj.x = 10;  // 🚩 Wrong: mutating existing object
    setObj(obj); // 🚩 Doesn't do anything

    Ви змінили існуючий об'єкт obj і передали його назад до setObj, тому React проігнорував оновлення. Щоб виправити це, вам потрібно переконатися, що ви завжди замінюєте об'єкти та масиви в стані замість того, щоб мутувати їх:

    // ✅ Correct: creating a new object
    setObj({
      ...obj,
      x: 10
    });

    Я отримую помилку: "Занадто багато повторних рендерингів"

    Ви можете отримати помилку, яка говорить: Too many re-renders. React limits the number of renders to prevent an infinite loop. Зазвичай це означає, що ви безумовно встановлюєте стан під час рендеру, тому ваш компонент потрапляє у цикл: рендер, відправка стану (яка викликає рендер), рендер, відправка стану (яка викликає рендер) і так далі. Дуже часто це спричинено помилкою у визначенні обробника події:

    // 🚩 Wrong: calls the handler during render
    return <button onClick={handleClick()}>Click me</button>
    
    // ✅ Correct: passes down the event handler
    return <button onClick={handleClick}>Click me</button>
    
    // ✅ Correct: passes down an inline function
    return <button onClick={(e) => handleClick(e)}>Click me</button>

    Якщо ви не можете знайти причину цієї помилки, натисніть на стрілку поруч з помилкою в консолі та перегляньте стек JavaScript, щоб знайти конкретний виклик функції set, відповідальний за помилку.


    Моя функція ініціалізації або оновлення запускається двічі

    У Суворому режимі React викликатиме деякі ваші функції двічі замість одного:

    function TodoList() {
      // This component function will run twice for every render.
    
      const [todos, setTodos] = useState(() => {
        // This initializer function will run twice during initialization.
        return createTodos();
      });
    
      function handleClick() {
        setTodos(prevTodos => {
          // This updater function will run twice for every click.
          return [...prevTodos, createTodo()];
        });
      }
      // ...

    Це очікувано і не повинно зламати ваш код.

    Така лише для розробки поведінка допомагає вам зберегти компоненти чистими. React використовує результат одного з викликів та ігнорує результат іншого. Поки ваш компонент, ініціалізатор та функції оновлення є чистими, це не повинно впливати на вашу логіку. Однак, якщо вони випадково стануть нечистими, це допоможе вам помітити помилки.

    Наприклад, ця нечиста функція оновлення змінює масив у стані:

    setTodos(prevTodos => {
      // 🚩 Mistake: mutating state
      prevTodos.push(createTodo());
    });

    Оскільки React викликає вашу функцію-редуктор двічі, ви побачите, що todo було додано двічі, і зрозумієте, що сталася помилка. У цьому прикладі ви можете виправити помилку шляхом заміни масиву замість його мутації:

    setTodos(prevTodos => {
      // ✅ Correct: replacing with new state
      return [...prevTodos, createTodo()];
    });

    Тепер, коли ця функція-редуктор є чистою, її виклик зайвий раз не впливає на поведінку. Ось чому повторний виклик цієї функції в React допомагає знаходити помилки. Чистими мають бути лише функції компонента, ініціалізатора та редуктора. Обробники подій не мають бути чистими, тому React ніколи не викликатиме ваші обробники подій двічі.

    Прочитайте Збереження компонентів у чистоті щоб дізнатися більше.


    Я намагаюся передати стан функції, але вона натомість викликається

    Ви не можете перевести функцію у такий стан:

    const [fn, setFn] = useState(someFunction);
    
    function handleClick() {
      setFn(someOtherFunction);
    }

    Оскільки ви передаєте функцію, React вважає, що someFunction є функцією-ініціалізатором , і що someOtherFunction є функцією оновлення, тому він намагається викликати їх і зберегти результат. Щоб насправді зберегти функцію, вам потрібно поставити перед ними () => в обох випадках. Тоді React збереже передані вами функції.

    const [fn, setFn] = useState(() => someFunction);
    
    function handleClick() {
      setFn(() => someOtherFunction);
    }