Wprowadzenie React #5 (2020) – Obsługa formularza i props key

W poprzednim wpisie pokazałem czym jest stan w komponentach React. W tym przyjrzymy się jak dynamicznie tworzyć komponenty, czym jest props key oraz jak obsłużyć prosty formularz.

Dynamiczne tworzenie komponentów

Na chwilę obecną komponenty ilość komponentów Image jest znana z góry, każdy komponent tworzymy ręcznie. Zmienimy tę sytuację.


Pozostałe części cyklu:

  1. Utworzenie projektu i pierwszy komponent
  2. Komponenty i props
  3. Obsługa zdarzeń
  4. Stan komponentu i przepływ danych
  5. Dynamiczne tworzenie komponentów, key i forumlarz
  6. Hook useEffect
  7. Zapisywanie danych w chmurze – Firebase

Jedyne czym różnią się owe komponenty to adres URL wyświetlanego obrazu. Wyciągnijmy je z propsów do tradycyjnej tablicy.

  const images = [
    "https://images.unsplash.com/photo-1508138221679-760a23a2285b",
    "https://images.unsplash.com/photo-1474487548417-781cb71495f3",
    "https://images.unsplash.com/photo-1580109672851-b85640868813",
    "https://images.unsplash.com/photo-1580046939256-c377c5b099f1",
    "https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
  ];

Następnie możemy skorzystać z funkcji map, by zamienić każdy element tablicy na coś innego i przypisać tablicę tak stworzonych komponentów do zmiennej. Na przykład na komponent React.

  const imgComponents = images.map(url => {
    return <Image url={url} setShowcaseURL={setShowcaseURL} />;
  });

JSX to tylko sposób na wygodniejsze zapisanie funkcji React.createElement, więc możemy go wykorzystywać również poza ‘return’. Zamiast kilklu komponentów Image możemy zwrócić zmienną imgComponents, pod którą to kryję się cały zestaw owych komponentów.

Finalnie nasz komponent App wygląda następująco

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  const images = [
    "https://images.unsplash.com/photo-1508138221679-760a23a2285b",
    "https://images.unsplash.com/photo-1474487548417-781cb71495f3",
    "https://images.unsplash.com/photo-1580109672851-b85640868813",
    "https://images.unsplash.com/photo-1580046939256-c377c5b099f1",
    "https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
  ];

  const imgComponents = images.map(url => {
    return <Image url={url} setShowcaseURL={setShowcaseURL} />;
  });

  return (
    <div>
      {imgComponents}
      <Showcase url={showcaseURL} />
    </div>
  );
};

Zdecydowanie bardziej kompaktowy i jasno przekazuję co się dzieje – dla każdego adresu URL tworzymy stosowny komponent Image.

Jednak jeśli otworzymy konsolę przeglądarki zobaczymy błąd

Each child in a list should have a unique “key” prop.

React key. Prop “key”

W przypadku, gdy dynamicznie tworzymy komponenty React potrzebuje, by każdy miał dodatkowego propsa key. Jest to unikalny identyfikator komponentu. W dużym uproszczeniu – jeśli nie podamy tego komponentu i stan jednego z komponentów z listy się zmieni (w naszym wypadku imgComponents) to React wyrysuje ponownie wszystkie komponenty, bo nie będzie wiedział, w którym konkretnie coś się zmieniło. Natomiast, gdy ten props będzie obecny przerysowany zostanie jeden, konkretny komponent z listy.

Dodajmy go. Musi on być unikalny, więc najlepszym kandydatem w naszym przypadku będzie po prostu adres URL obrazu. Oczywiście nie jest to idealny kandydat, bo jeśli podamy dwa identyczne URL to i klucz będzie się powtarzał, ale zostawimy sobie ten przypadek bez rozwiązania.

  const imgComponents = images.map(url => {
    return <Image url={url} key={url} setShowcaseURL={setShowcaseURL} />;
  });

Warning nie powinien się już pokazywać.

Obsługa prostych formularzy

Wszystko fajnie, komponenty tworzą się dynamiczne, ale ich zawartość zbyt dynamiczna nie jest.

Dodajmy więc opcję dodawania adresów wyświetlanych obrazów przez użytkownika strony, czyli stary, dobry input. Stworzymy go w komponencie App wraz z guzikiem do dodawania.

<div>
      <form>
        <input type="text" placeholder="URL obrazu..." value=""></input>
        <button onClick="">Dodaj</button>
      </form>
      {imgComponents}
      <Showcase url={showcaseURL} />
</div>

By React mógł na żywo renderować dodawane obrazy, ich lista musi być przechowywana jako stan. Tylko w ten sposób może on wykryć jakąkolwiek zmianę i stosownie zareagować.

Dodajemy więc kolejny hook setState tym razem z tablicą jako wartością. Przy okazji zmieniliśmy nazwę zmiennej z początkowym zestawem obrazów.

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  const defaultImages = [
    "https://images.unsplash.com/photo-1508138221679-760a23a2285b",
    "https://images.unsplash.com/photo-1474487548417-781cb71495f3",
    "https://images.unsplash.com/photo-1580109672851-b85640868813",
    "https://images.unsplash.com/photo-1580046939256-c377c5b099f1",
    "https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
  ];

  const [images, setImages] = useState(defaultImages);

  const imgComponents = images.map(url => {
    return <Image url={url} key={url} setShowcaseURL={setShowcaseURL} />;
  });

  return (
    <div>
      <form>
        <input type="text" placeholder="URL obrazu..." value=""></input>
        <button onClick="">Dodaj</button>
      </form>
      {imgComponents}
      <Showcase url={showcaseURL} />
    </div>
  );
};

W następnym kroku musimy wartość z input dodawać do tablicy.

Ponownie jednak musimy aktualną wartość input boxa trzymać jako osobny stan (const [newImageUrl, setNewImageUrl] = useState(“”)) i przypisać go jako value (<input … value={newImageUrl}/>). Natomiast do zdarzenia onChange na elemencie input musimy przypisać metodę służącą do zmiany danego stanu (<input … onChange={e => setNewImageUrl(e.target.value)}).

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  const defaultImages = [
    "https://images.unsplash.com/photo-1508138221679-760a23a2285b",
    "https://images.unsplash.com/photo-1474487548417-781cb71495f3",
    "https://images.unsplash.com/photo-1580109672851-b85640868813",
    "https://images.unsplash.com/photo-1580046939256-c377c5b099f1",
    "https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
  ];

  const [images, setImages] = useState(defaultImages);
  const [newImageUrl, setNewImageUrl] = useState("");

  const imgComponents = images.map(url => {
    return <Image url={url} key={url} setShowcaseURL={setShowcaseURL} />;
  });

  return (
    <div>
      <form>
        <input
          type="text"
          placeholder="URL obrazu..."
          value={newImageUrl}
          onChange={e => setNewImageUrl(e.target.value)}
        ></input>
        <button onClick="">Dodaj</button>
      </form>
      {imgComponents}
      <Showcase url={showcaseURL} />
    </div>
  );
};

Kolejnym krokiem jest dodanie wprowadzonego URL do tablicy na podstawie której tworzone są komponenty Image. Powinniśmy jeszcze weryfikować czy to co wprowadził użytkownik w polu tekstowym to faktycznie URL, a nie jakieś głupoty, ale pominiemy ten krok i skupimy się na dodaniu do tablicy.

Dodawanie nowego elementu tablicy zrobimy przy wywołaniu wydarzenia onClick na guziku dodaj (teraz mamy tam pustki “”).

Po pierwsze trzeba będzie zatrzymać domyślną obługę zdarzenia i jego propagowanie.

Następnie zostanie ustawiony nowy stan za pomocą metody setImages. Zastosujemy tam destrukturyzację, czyli rozbicie tablicy na pojedyczne elementy, które to użyjemy do utworzenia nowej tablicy rozszerzonej o element newImageUrl.

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  const defaultImages = [
    "https://images.unsplash.com/photo-1508138221679-760a23a2285b",
    "https://images.unsplash.com/photo-1474487548417-781cb71495f3",
    "https://images.unsplash.com/photo-1580109672851-b85640868813",
    "https://images.unsplash.com/photo-1580046939256-c377c5b099f1",
    "https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
  ];

  const [images, setImages] = useState(defaultImages);
  const [newImageUrl, setNewImageUrl] = useState("");

  const imgComponents = images.map(url => {
    return <Image url={url} key={url} setShowcaseURL={setShowcaseURL} />;
  });

  const addImage = event => {
    event.preventDefault();
    event.stopPropagation();
    setImages([...images, newImageUrl]);
  };

  return (
    <div>
      <form>
        <input
          type="text"
          placeholder="URL obrazu..."
          value={newImageUrl}
          onChange={e => setNewImageUrl(e.target.value)}
        ></input>
        <button onClick={addImage}>Dodaj</button>
      </form>
      {imgComponents}
      <Showcase url={showcaseURL} />
    </div>
  );
};
react key

Na tym zakończymy ten wpis. Źródła dostępne są na GitHub.

Tradycyjnie zapraszam do dołączenia do newslettera, gdzie materiały odnośnie React również mają swoje miejsce.

2 myśli w temacie “Wprowadzenie React #5 (2020) – Obsługa formularza i props key”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *