Własne hooki
Tworzenie własnych hooków wymaga doświadczenia w pracy z hookami i React. Na ten moment nie staraj się skupiać na tworzeniu w projekcie własne hooków. Jeżeli widzisz wyraźną możliwość powtórnego wykorzystania kodu - wspaniale, wtedy utwórz hooka. W przeciwnym razie lepiej skoncentrować się na nauce podstawowego materiału i wykorzystaniu wbudowanych hooków React lub gotowych hooków z bibliotek jak np. react-use.
Podstawowym zadaniem hooków jest umożliwienie powtórnego wykorzystania kodu (logiki). Tworzenie własnych hooków to proces wydobycia logiki komponentów do funkcji, co sprawi, że kod projektu będzie czystszy i łatwiejszy do zrozumienia.
Hook to po prostu funkcja, której nazwa powinna zaczynać się od przedrostka use. Na jej podstawie React będzie decydował, czy to zwykła funkcja, czy hook (np useState, useEffect, useToggle, useDevice, useImages i tak dalej). Własne hooki tworzone są wewnątrz ciała komponentu lub w oddzielnych plikach. Mogą również wywoływać inne hooki (analogicznie jak komponenty).
Hook useToggle
W poniższym przykładzie mamy dwa komponenty, które zawierają analogiczną logikę otwierania, zamykania oraz przełączania elementu interfejsu, na przykład okna modalnego.
// ComponentA.jsx
const ComponentA = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<>
<button onClick={openModal}>Open modal</button>
<ModalA isOpen={isModalOpen} onClose={closeModal} />
</>
);
};
// ComponentB.jsx
const ComponentB = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<>
<button onClick={openModal}>Open modal</button>
<ModalB isOpen={isModalOpen} onClose={closeModal} />
</>
);
};
Komponentów, które będą potrzebować analogicznej logiki może być w projekcie bardzo dużo. Stwórzmy więc własny hook useToggle, w którym ukryjemy stan i funkcje do jego aktualizacji. Pozwoli nam to na powtórne wykorzystanie kodu i ograniczenie jego ilości w ciele komponentów.
src/hooks/useToggle.js
export const useToggle = () => {
const [isOpen, setIsOpen] = useState(false);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const toggle = () => setIsOpen(isOpen => !isOpen);
return { isOpen, open, close, toggle };
};
Własny hook może przyjmować dowolne argumenty i zwracać cokolwiek. W naszym przypadku to obiekt z czterema właściwościami.
Wykorzystując naszego hooka, kod komponentów będzie wyglądał następująco:
// ComponentA.jsx
import { useToggle } from "path/to/hooks/useToggle.js";
const ComponentA = () => {
const { isOpen, open, close } = useToggle();
return (
<>
<button onClick={open}>Open modal</button>
<ModalA isOpen={isOpen} onClose={close} />
</>
);
};
// ComponentB.jsx
import { useToggle } from "path/to/hooks/useToggle.js";
const ComponentB = () => {
const { isOpen, open, close } = useToggle();
return (
<>
<button onClick={open}>Open modal</button>
<ModalB isOpen={isOpen} onClose={close} />
</>
);
};
Nawet w tak prostym przykładzie udało nam się znacznie zredukować powielanie kodu. Ssprawiliśmy, że komponenty są czystsze, a ewentualny refaktor kodu będzie łatwiejszy do wykonania.
Jako, że hooki są zwykłymi funkcjami to można im przekazywać argumenty, np. początkową wartość stanu. Rozszerzmy useToggle tak, aby można było ustawić okno modalne jako początkowo otwarte. Natomiast domyślnie będzie zamknięte.
// src/hooks/useToggle.js
export const useToggle = (initialState = false) => {
const [isOpen, setIsOpen] = useState(initialState);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const toggle = () => setIsOpen(isOpen => !isOpen);
return { isOpen, open, close, toggle };
};
// MyComponent.jsx
import { useToggle } from "path/to/hooks/useToggle.js";
const MyComponent = () => {
const { isOpen, open, close } = useToggle(true);
return (
<>
<button onClick={open}>Open modal</button>
<Modal isOpen={isOpen} onClose={close} />
</>
);
};
Hook useFormValue
import { useState } from "react";
export const useFormValue = initValue => {
const [value, setValue] = useState(initValue);
return {
value,
onChange: e => {
setValue(e.target.value || e.target.innerText);
}
};
};
Mamy możliwość przekazania początkowej wartości (initValue), która zostanie zapisana przy pomocy useState. Następnie zwracamy informacje o value oraz funkcję onChange, która to ustawia lokalne value na podstawie e.target.value lub e.targer.innerText. Użycie wygląda następująco:
import { useFormValue } from "./hooks/useFormValue"
const App = () => {
const login = useFormValue("")
const age = useFormValue("initial value")
return (
<>
<input type="text" {...login} />
<input type="text" {...age} />
</>
)
}
Wykorzystanie hooka także jest bardzo przyjazne, ponieważ nie wymaga wiele od dewelopera. Pozwala nam natomiast przenieść część logiki, która się powtarza, do oddzielnego hooka.
Poniżej link do wielu przykładów hooków, takich jak useCookie, useLocalStorage, useTitle czy kolejny useToggle.
Więcej przykładów hooków