Уроки по веб-разработке

useState() — хук состояния в ReactJS

useState() — это хук, который предназначен для работы с состоянием компонента в приложениях на React и работает только в функциональных компонентах.

Для чего нужен useState()

Данный хук используется для:

Самый простой пример использования:

import React, { useState } from "react";

export default function App() {
  const [state, setState] = useState('Hello World');

  return (
    <div className="App">
      <h1>{ state }</h1>
    </div>
  );
}

Хук useState() возвращает состояние (state) и функцию для его изменения (setState). Название данной функции и переменной задаете вы сами. C помощью деструктуризации ([state, setState]) мы сразу получаем значения.

В качестве параметра хук принимает значение по умолчанию. В нашем случае это строка Hello World.

Данный пример очень простой и в такой реализации нам состояние даже не понадобиться, мы могли бы сразу в заголовок прописать данную строку.

Давайте рассмотрим другой пример. У нас есть поле ввода и в зависимости от того, что мы вводим, будет изменяться заголовок:

import React, { useState } from "react";

export default function App() {
  const [title, setTitle] = useState('');

  const changeTitle = (e) => {
    const newTitle = e.target.value;

    setTitle(newTitle);
  }

  return (
    <div className="App">
      <h1>{ title }</h1>
      <input type='text' name='title' value={title} onChange={(e) => changeTitle(e)} />
    </div>
  );
}

Теперь у нас состояние называется title и функция, которая будет его изменять — setTitle. Добавляем input с обработчиком изменений changeTitle. В обработчике мы получаем значение из инпута (e.target.value) и с помощью функции для обновления состояния setTitle меняем его на новый.

Сейчас независимо от того, есть у нас title или нет, тег h1 будет отображаться все равно. На этом же примере мы можем рассмотреть условный рендеринг. В примере ниже если title не задан, тогда тег <h1> не будет отображаться:

return (
    <div className="App">
      { title && <h1>{ title }</h1> }
      <input type='text' name='title' value={title} onChange={(e) => changeTitle(e)} />
    </div>
  );

Также возможен другой вариант. Вы можете в зависимости от условия делать разный return

if (title)
    return <h1>{ title }</h1>

return (
    <input type='text' name='title' value={title} onChange={(e) => changeTitle(e)} />
  );

Если title задан отрисовываем его, если нет отрисовываем инпут для ввода тайтла.

В состоянии компонента вы можете хранить строки, числа, массивы, объект и т.д.

Сохранение данных с сервера с помощью хука useState()

Для хранения данных полученных с сервера нам дополнительно понадобиться хук useEffect():

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

export default function App() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => response.json())
      .then((json) => setPosts(json));
  }, []);

  if (!posts.length) return <h4>Loading...</h4>; 

  return (
    <>
      <h1>POSTS</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </>
  );
}

Теперь давайте рассмотрим, что у нас есть. У нас есть api (https://jsonplaceholder.typicode.com/posts), который возвращает нам массив из 100 постов. Все асинхронные действия в функциональных компонентах должны делаться с помощью хука useEffect, поэтому мы с помощью fecth получаем посты и дальше сохраняем их в posts с помощью функции setPosts.

Далее, чтобы сделать список постов, мы проходимся методом map() по массиву и на каждой итерации отрисовываем элемент li с уникальным ключом key.

Использование useState() с объектом

Рассмотрим теперь такой пример, где у нас state это объект и нам необходимо будет его обновлять:

import React, { useState } from "react";

export default function App() {
  const [user, setUser] = useState({});

  const handleFieldChange = (e) => {
    const { name: fieldName, value: fieldValue } = e.target;

    const updatedUser = {
      ...user,
      [fieldName]: fieldValue
    };

    setUser(updatedUser);
  };

  return (
    <>
      <h1>USER</h1>
      <ul>
        <li>First Name: {user.firstName || ""}</li>
        <li>Last Name: {user.lastName || ""}</li>
        <li>Age: {user.age || ""}</li>
      </ul>
      <hr />
      <form>
        <label>
          Name
          <input
            type="text"
            name="firstName"
            onChange={(e) => handleFieldChange(e)}
          />
        </label>
        <label>
          Last Name
          <input
            type="text"
            name="lastName"
            onChange={(e) => handleFieldChange(e)}
          />
        </label>
        <label>
          Age
          <input
            type="number"
            name="age"
            onChange={(e) => handleFieldChange(e)}
          />
        </label>
      </form>
    </>
  );
}

У нас есть user, по умолчанию это пустой объект, и есть форма ниже для указания необходимых полей пользователя. У всех полей формы есть одинаковый обработчик — handleFieldChange, с его помощью мы получаем вводимые значения, далее с помощью деструктуризации мы получаем имя и значение поля, создаем новый объект, куда помещаем уже существующие поля у user (строка 10) и добавляем новые поля. Затем обновляем state.