Jak zamiast wielu useState używać useReducer?

React

readTime

3 min

Jak zamiast wielu useState używać useReducer?

Czy kiedykolwiek znalazłeś się w sytuacji, w której musiałeś użyć zbyt wielu hoków useState w jednym komponencie? Oto przykład tego, o czym mówię:

js
import { useState } from "react";

export const App = () => {
  const [name, setName] = useState("");
  const [something, setSomething] = useState("");
  const [email, setEmail] = useState("");
  const [phone, setPhone] = useState("");
  const [courseStartDate, setCourseStartDate] = useState(null);
  const [courseEndDate, setCourseEndDate] = useState(null);
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <p>{name} </p>
    </div>
  );
};

Załóżmy, że powyższy komponent jest używany do aktualizacji danych użytkownika, który zapisał się na kurs. Zajmijmy się najpierw wszystkimi problemami związanymi z powyższym podejściem, a następnie omówimy rozwiązania i alternatywy w tym artykule

Jaki jest w tym problem?

Po pierwsze, nie ma żadnych zabezpieczeń. Na przykład, w tym podejściu nie ma sposobu, aby sprawdzić, czy courseEndDate powinien być dzień po courseStartDate. Nie ma również zabezpieczenia dla nazwy, która nie powinna zawierać żadnych liczb itp.

Oczywiście możemy stworzyć osobne handler'y dla wszystkich tych funkcjonalności, ale to sprawia, że kod jest zbyt długi i nieporęczny. Zawsze lepiej jest mieć je w jednym miejscu dla wszystkich tych zabezpieczeń.

js
import { useState } from "react";

export const App = () => {
  const [name, setName] = useState("");
  const [something, setSomething] = useState("");
  const [email, setEmail] = useState("");
  const [phone, setPhone] = useState("");
  const [courseStartDate, setCourseStartDate] = useState(null);
  const [courseEndDate, setCourseEndDate] = useState(null);

  const handleNameChange = (e) => {
    const hasNumber = /\d$/.test(e.target.value);
    if (!hasNumber) setName(e.target.value);
  };

  const handleEndDate = (value) => {
    if (courseEndDate > courseStartDate) setCourseEndDate(value);
  };
  return (
    <div>
      <input value={name} onChange={handleNameChange} />
      <p>{name}</p>
    </div>
  );
};

Jaka jest alternatywa?

Używając useReducer, moglibyśmy przekształcić powyższy kod w następujący sposób:

js
import { useReducer } from "react";

export const App = () => {
  const [data, updateData] = useReducer(
    (prev, next) => {
      const updatedData = { ...prev, ...next };

      const hasNumber = /\d$/.test(updatedData.name);
      if (!hasNumber) updatedData.name = prev.name;

      if (updatedData.courseEndDate > updatedData.courseStartDate)
        updatedData.courseEndDate = updateData.courseStartDate;

      return updatedData;
    },
    {
      name: "",
      email: "",
      something: "",
      phone: "",
      courseStartDate: null,
      courseEndDate: null,
    }
  );
  return (
    <div>
      <input
        value={data.name}
        onChange={(e) => updateData({ name: e.target.value })}
      />
      <p>{name}</p>
    </div>
  );
};

Jak widać powyżej, wszystkie zabezpieczenia mamy w jednej funkcji i założę się, że dzięki takiemu podejściu kod będzie wyglądał jeszcze czyściej.

Dlaczego nie useState?

Ale czekaj, możesz również zastanawiać się, czy możesz zrobić to samo z samym useState bez użycia useReducer. Ale jest tu jeden haczyk. Z useState będziesz miał pojedynczy stan ze wszystkimi właściwościami, ale nie będziesz w stanie mieć wszystkich tych zabezpieczeń w jednym miejscu.

Jedno ważne ostrzeżenie

Jak zauważyłeś w powyższym kodzie, użyliśmy operatora spread(...), aby upewnić się, że nie zmutujemy obiektu. Musimy tutaj pamiętać o niezmienności (immutability), ponieważ jeśli zmutujemy, spowoduje to, że React nie wyrenderuje się zgodnie z oczekiwaniami.

Dzięki temu modelowi masz scentralizowane miejsce w kodzie, aby dodać zabezpieczenia w razie potrzeby. Wystarczy wywołać updateData() i to wszystko. Jak widzisz masz wiele możliwości konfiguracji w prosty sposób.

Wnioski

Nie oznacza to, że nigdy nie powinniśmy używać useState. Oba hooki są równie ważne i musimy podjąć właściwą decyzję, kiedy czego użyć. Zawsze należy pamiętać, że wartość stanu hooka useReducer musi być traktowana jako niezmienna. Zawsze możemy połączyć to z useContext, aby współdzielić stan między różnymi komponentami, co czyni to jeszcze potężniejszym. Mam nadzieję, że materiał był dla Ciebie przydatny i zapraszam do kolejnych ;)

źródło: https://javascript.plainenglish.io/stop-using-too-many-usestate-in-react-e613e021b33c

authorImg

Witek Pruchnicki

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