Co to jest useState() i jak działa?

React

readTime

9 min

Co to jest useState() i jak działa?

React od wersji 16.8 posiada Hooki, które umożliwiają zarządzanie stanem oraz wykonywanie tzw. efektów ubocznych w komponentach funkcyjnych.

Jednym z najbardziej podstawowych i powszechnie używanych hooków jest useState.

W tym artykule wyjaśnię Ci:

  • czym jest useState,
  • jak działa
  • jak można go używać w różnych sytuacjach
  • dobre praktyki w kwestii użycia useState()
  • najczęściej popełniane błędy
  • przykładowe zadania rekrutacyjne z zastosowaniem hooka useState() .

Co to są Hooki w Reakcie?

Hooki to specjalne funkcje w React, które pozwalają na "zaczepienie" się w stan i cykl życia komponentów funkcyjnych.

Wprowadzone w Reakcie 16.8, hooki przekształciły sposób, w jaki twórcy aplikacji pracują z komponentami funkcyjnymi, umożliwiając im wykorzystanie funkcji, które wcześniej były dostępne tylko w komponentach klasowych.

🚀 Dlaczego Hooki to obecnie fundament Reacta?

  • Prostota: Hooki umożliwiają użycie stanu i innych funkcji Reacta bez potrzeby tworzenia klas. Dzięki nim komponenty funkcyjne mogą być bardziej czytelne i prostsze w zarządzaniu.
  • Reużywalność: Możesz tworzyć własne hooki (custom hooks), co pozwala na reużywanie logiki między różnymi komponentami.
  • Elastyczność: Hooki dają większą kontrolę nad stanem i efektami w komponentach, co czyni je bardziej elastycznymi i łatwiejszymi w użyciu.

Dlaczego potrzebujemy Hooków?

Przed wprowadzeniem hooków, zarządzanie stanem w Reactie było ograniczone do komponentów klasowych. Hooki, takie jak useState, pozwalają na:

  • Zarządzanie stanem bez potrzeby tworzenia komponentów klasowych.
  • Unikanie złożoności związanej z metodami cyklu życia komponentów.
  • Ułatwienie współdzielenia logiki między różnymi komponentami.

Co to jest useState?

useState to hook, który umożliwia dodanie stanu do komponentu funkcyjnego. Używając useState, możemy zarządzać zmiennymi stanu w komponentach, które wcześniej musiałyby być komponentami klasowymi.

Jak działa useState?

Deklaracja useState wygląda następująco:

javascript
const [state, setState] = useState(initialState);
  • state: Aktualna wartość stanu.
  • setState: Funkcja służąca do aktualizacji wartości stanu.
  • initialState: Wartość początkowa stanu.

useState zwraca tablicę zawierającą dwa elementy: aktualny stan oraz funkcję do jego aktualizacji. Dzięki destrukturyzacji tablicy możemy nadać tym elementom dowolne nazwy.

Podstawowy Przykład użycia useState

Rozważmy prosty przykład komponentu, który wyświetla licznik i przycisk, który zwiększa wartość licznika o 1 za każdym razem, gdy zostanie kliknięty.

Przykład bez użycia useState:

javascript
import React from "react";
import ReactDOM from "react-dom";

let count = 0;

function increase() {
  count++;
  ReactDOM.render(
    <div className="container">
      <h1>{count}</h1>
      <button onClick={increase}>+</button>
    </div>,
    document.getElementById("root")
  );
}

ReactDOM.render(
  <div className="container">
    <h1>{count}</h1>
    <button onClick={increase}>+</button>
  </div>,
  document.getElementById("root")
);

W powyższym kodzie za każdym razem, gdy przycisk jest klikany, musimy ręcznie renderować komponent, aby zaktualizować wyświetlaną wartość licznika. Jest to nieefektywne i niezgodne z filozofią Reacta.

Przykład z użyciem useState:

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

function App() {
  const [count, setCount] = useState(0);

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

  return (
    <div className="container">
      <h1>{count}</h1>
      <button onClick={increase}>+</button>
    </div>
  );
}

Dzięki użyciu useState, możemy łatwo zarządzać stanem licznika i automatycznie aktualizować interfejs użytkownika po każdej zmianie stanu.

Głębsze Zrozumienie useState

Inicjalizacja Stanu

Stan zadeklarowany przez useState może być inicjalizowany nie tylko wartościami prymitywnymi, ale również obiektami czy funkcjami.

javascript
const [user, setUser] = useState({ name: "John", age: 30 });

Możemy również przekazać funkcję jako argument do useState, aby obliczyć stan początkowy tylko raz, podczas pierwszego renderowania komponentu.

javascript
const [count, setCount] = useState(() => computeExpensiveInitialState());

Aktualizacja Stanu

Funkcja setState może być użyta do aktualizacji stanu. Istnieją dwa główne sposoby aktualizacji stanu:

  1. Bezpośrednie ustawienie nowej wartości stanu:

    javascript
    setCount(count + 1);
    
  2. Funkcja aktualizująca (gdy nowa wartość stanu zależy od poprzedniego stanu):

    javascript
    setCount((prevCount) => prevCount + 1);
    

Zarządzanie Złożonym Stanem

Jeśli stan jest obiektem z wieloma właściwościami, musimy być ostrożni podczas aktualizacji, aby nie nadpisać całego obiektu.

javascript
const [state, setState] = useState({ name: "John", age: 30 });

function updateName(newName) {
  setState((prevState) => ({
    ...prevState,
    name: newName,
  }));
}

Zaawansowane Przykłady użycia useState

Przełącznik Światła

Stwórzmy przykład przełącznika światła, który zmienia kolor tła między białym a czarnym.

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

function LightSwitch() {
  const [isOn, setIsOn] = useState(false);

  function toggleLight() {
    setIsOn(!isOn);
  }

  return (
    <div
      style={{
        height: "100vh",
        background: isOn ? "white" : "black",
        color: isOn ? "black" : "white",
      }}
      onClick={toggleLight}
    >
      {isOn ? "Light is On" : "Light is Off"}
    </div>
  );
}

Formularz Logowania

Przykład formularza logowania, który wykorzystuje useState do zarządzania danymi wejściowymi.

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

function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  function handleSubmit(event) {
    event.preventDefault();
    alert(`Email: ${email}, Password: ${password}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
      </div>
      <button type="submit">Login</button>
    </form>
  );
}

Zegar z Aktualnym Czasem

Stwórzmy zegar, który pokazuje aktualny czas i aktualizuje się co sekundę.

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

function Clock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const timer = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(timer); // Wyczyść setInterval podczas odmontowania komponentu
  }, []);

  return <h1>{time.toLocaleTimeString()}</h1>;
}

Optymalizacja i Najlepsze Praktyki

Unikanie Niepotrzebnych Renderów

Używając setState, warto unikać niepotrzebnych renderów komponentów. Należy zadbać o to, aby aktualizować stan tylko wtedy, gdy jest to konieczne.

javascript
function increment() {
  if (count < 10) {
    setCount(count + 1);
  }
}

Łączenie Stanów

Chociaż można używać wielu useState do zarządzania różnymi częściami stanu, warto zastanowić się nad łączeniem ich w jeden bardziej złożony obiekt, jeśli te stany są ściśle powiązane.

javascript
const [formState, setFormState] = useState({
  name: "",
  email: "",
  password: "",
});

function updateFormState(field, value) {
  setFormState({
    ...formState,
    [field]: value,
  });
}

Debugowanie

Używanie hooków takich jak useState może sprawić, że debugowanie stanu stanie się bardziej skomplikowane. Warto używać narzędzi takich jak React Developer Tools, które ułatwiają śledzenie stanu komponentów.

Najczęściej popełniane błędy przy używaniu useState

1. Aktualizacja stanu bez funkcji aktualizującej

Jednym z częstych błędów jest próba bezpośredniej aktualizacji stanu zamiast użycia funkcji setState.

Błąd:

javascript
const [count, setCount] = useState(0);

function wrongIncrement() {
  count++; // Błąd: bezpośrednia modyfikacja stanu
}

Poprawnie:

javascript
function correctIncrement() {
  setCount(count + 1);
}

2. Brak inicjalizacji stanu

Inicjalizowanie stanu w useState jest kluczowe. Nie można wywołać useState bez podania wartości początkowej.

Błąd:

javascript
const [count, setCount] = useState(); // Błąd: brak wartości początkowej

Poprawnie:

javascript
const [count, setCount] = useState(0); // Poprawne: wartość początkowa jest ustawiona

3. Ignorowanie poprzedniego stanu przy aktualizacji

Jeśli nowy stan zależy od poprzedniego, należy użyć funkcji aktualizującej, aby uniknąć błędów.

Błąd:

javascript
const [count, setCount] = useState(0);

function increment() {
  setCount(count + 1); // Może spowodować błędy w zależności od renderowania
}

Poprawnie:

javascript
function safeIncrement() {
  setCount((prevCount) => prevCount + 1); // Bezpieczne użycie poprzedniego stanu
}

4. Używanie useState poza komponentem funkcyjnym

Hooki muszą być używane wewnątrz komponentów funkcyjnych lub innych hooków, nie można ich wywoływać w zwykłych funkcjach lub poza komponentami.

Błąd:

javascript
function useStateOutsideComponent() {
  const [state, setState] = useState(0); // Błąd: użycie poza komponentem
}

Poprawnie:

javascript
function MyComponent() {
  const [state, setState] = useState(0); // Poprawne: użycie wewnątrz komponentu funkcyjnego
  return <div>{state}</div>;
}

5. Zapomnienie o funkcji czyszczącej po efektach

Przy używaniu useState w połączeniu z useEffect, ważne jest, aby zadbać o czyszczenie zasobów, aby uniknąć wycieków pamięci.

Błąd:

javascript
useEffect(() => {
  const interval = setInterval(() => {
    setCount((prevCount) => prevCount + 1);
  }, 1000);

  // Brak funkcji czyszczącej
}, []);

Poprawnie:

javascript
useEffect(() => {
  const interval = setInterval(() => {
    setCount((prevCount) => prevCount + 1);
  }, 1000);

  return () => clearInterval(interval); // Funkcja czyszcząca
}, []);

Dobre praktyki przy używaniu useState

1. Grupa powiązanych stanów w jednym obiekcie

Jeśli masz kilka powiązanych wartości stanu, rozważ przechowywanie ich w jednym obiekcie, aby ułatwić zarządzanie.

javascript
const [user, setUser] = useState({ name: "", age: 0 });

function updateUser(field, value) {
  setUser((prevUser) => ({
    ...prevUser,
    [field]: value,
  }));
}

2. Rozdzielenie logiki aktualizacji od komponentu

Aby zachować czystość komponentu, logika aktualizacji stanu może być przeniesiona do zewnętrznych funkcji lub hooków.

javascript
function useCounter(initialValue) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return { count, increment, decrement };
}

function CounterComponent() {
  const { count, increment, decrement } = useCounter(0);

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

3. Używanie wbudowanych typów Hooków

React Developer Tools oferują wsparcie dla wbudowanych hooków, co ułatwia debugowanie i śledzenie stanu. Używaj wbudowanych hooków zamiast tworzyć własne, jeśli jest to możliwe.

4. Unikanie złożonego stanu w jednym hooku

Jeśli stan jest zbyt złożony, rozważ podzielenie go na kilka useState lub rozważ użycie useReducer dla bardziej złożonej logiki aktualizacji stanu.

javascript
const [name, setName] = useState("");
const [age, setAge] = useState(0);

5. Optymalizacja użycia pamięci

Jeśli stan początkowy jest wynikiem złożonego obliczenia, przekaż funkcję do useState, aby obliczyć go tylko raz, przy pierwszym renderze.

javascript
const [expensiveState, setExpensiveState] = useState(() =>
  computeExpensiveState()
);

Podchwytliwe sytuacje z useState

1. Asynchroniczne aktualizacje

Stany w React są aktualizowane asynchronicznie, co może prowadzić do podchwytliwych sytuacji, jeśli próbujesz używać aktualnej wartości stanu zaraz po jej ustawieniu.

Błąd:

javascript
const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);
  console.log(count); // Może nie wyświetlić zaktualizowanej wartości
}

Poprawnie:

javascript
useEffect(() => {
  console.log(count); // Zawsze pokaże aktualną wartość
}, [count]);

2. Efekty uboczne w funkcji renderującej

Nie umieszczaj funkcji setState lub innych efektów ubocznych bezpośrednio w funkcji renderującej, ponieważ może to prowadzić do nieskończonych pętli renderowania.

Błąd:

javascript
const [count, setCount] = useState(0);

function App() {
  setCount(count + 1); // Błąd: powoduje nieskończoną pętlę renderowania
  return <h1>{count}</h1>;
}

Poprawnie:

javascript
function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(count + 1);
  }, []);

  return <h1>{count}</h1>;
}

Podsumowanie

useState jest jednym z najważniejszych narzędzi w Reakcie, które umożliwia dodanie stanu do komponentów funkcyjnych. Dzięki useState możemy zarządzać i aktualizować stan aplikacji w sposób efektywny i zrozumiały.

Od prostych liczników po złożone formularze i dynamiczne interfejsy, useState dostarcza elastyczność potrzebną do budowy interaktywnych aplikacji.

Hooki, takie jak useState, zmieniają sposób, w jaki programujemy w Reakcie, umożliwiając nam tworzenie bardziej modularnych i łatwiejszych do zrozumienia komponentów.

Jeśli chcesz dowiedzieć się więcej o hookach, sprawdź również inne popularne hooki, takie jak useEffect i useContext.

Mam nadzieję, że ten artykuł pomógł Ci zrozumieć, czym jest useState i jak można go efektywnie wykorzystać w Twoich projektach Reactowych!

authorImg

Witek Pruchnicki

Z pasją dzielę się wiedzą o programowaniu i nie tylko na różne sposoby