React.js w przykładach: filtrowanie statycznej listy

Ten wpis jest 11 częścią z 29 w kursie React.js

W jednym z komentarzy ktoś zasugerował mi, abym pokazywał jak najwięcej praktycznych przykładów. Inna osoba pytała konkretnie o przykład filtrowania listy na podstawie tekstu wpisywanego w input. Stwierdziłem, że warto skorzystać z tych sugestii 🙂 Oto powstaje seria wpisów, które będą się przeplatały z kursem Reacta jako takim. Tutaj będę pokazywał konkretne przykłady i implementacje, bez tłumaczenia teorii. Pierwszym przykładem będzie właśnie taka lista — na razie wersja prosta, ze statycznymi danymi i synchronicznym wyszukiwaniem. Do dzieła!

Plan działania

Chcesz stworzyć listę (np. kontaktów) i wyrenderować ją. Łatwizna. Do tego potrzebujemy input, który będzie wyszukiwarką. Wpisanie czegoś w input ma powodować filtrowanie listy. Dodatkowo jeśli nic nie znaleziono — ma wyświetlić się komunikat.

Myślę, że ten przykład w całości zamknie się w jednym/dwóch komponentach.

HTML

Najpierw sam HTML:

<input type="search">
<ul>
  <li>Michał</li>
  <li>Ania</li>
  <li>Kasia</li>
  <li>Tomek</li>
</ul>

No łatwiej chyba się nie da 😉

React.js

Lista użytkowników

Tworzę dwa komponenty: App oraz UsersList. UsersList ma być typowym komponentem „głupim” — tzn. jego renderowanie zależy tylko od przekazanych propsów. Przekażę tam tablicę z listą kontaktów już po przefiltrowaniu, którą zmapuję na listę elementów:

const UsersList = ({ users }) => {
  return (
    <ul>
      {users.map(user => <li key={user}>{user}</li>)}
    </ul>
  );
};

Pamiętaj o dodaniu unikalnego atrybutu key do każdego elementu zawsze gdy renderowana jest tablica!

A co gdy brak wyników?

Dodaję jeden warunek i renderuję co innego. A więc ostatecznie ten komponent wygląda tak:

const UsersList = ({ users }) => {
  if (users.length > 0) {
    return (
      <ul>
        {users.map(user => <li key={user}>{user}</li>)}
      </ul>
    );
  }

  return (
    <p>No results!</p>
  );
};

Komponent App

Logikę filtrowania oraz obsługę zdarzeń zamknę w komponencie App. Mógłbym się pokusić o dalszy podział na mniejsze komponenty, ale przy tak prostym przykładzie nie widzę w tym sensu. Przefiltrowanych użytkowników przechowuję w state i przekazuję do UsersList. Do inputa podpinam obsługę jednego zdarzenia onInput:

class App extends React.Component {
  constructor() {
    super();

    this.state = {
      filteredUsers: allUsers
    };
  }

  render() {
    return (
      <div>
        <input onInput={this.filterUsers.bind(this)} />
        <UsersList users={this.state.filteredUsers} />
      </div>
    );
  }
};

Stała allUsers pochodzi „z zewnątrz” — w sumie nieistotne skąd, bo nie mam tutaj żadnego API, store’a ani nic takiego. Po prostu zdefiniuj ją sobie gdziekolwiek. Przynajmniej w tym przykładzie 🙂

Implementacja filtrowania

Samo filtrowanie sprowadza się do:

  1. Pobrania wpisywanego tekstu z inputa
  2. Przefiltrowania tablicy wedle pewnych kryteriów (*)
  3. Ustawienia state

Punkt 2 oznaczyłem gwiazdką, bo to będzie jeszcze osobna funkcja. Kroki 1-3 łatwo zaimplementować:

filterUsers(e) {
  const text = e.currentTarget.value;
  const filteredUsers = this.getFilteredUsersForText(text)
  this.setState({
    filteredUsers
  });
}

Implementacja getFilteredUsersForText

W tym przypadku ta funkcja działa dość prosto. Filtruję zawartość tablicy na podstawie porównania elementu z wpisanym tekstem. Jeśli element zawiera fragment wpisanego tekstu to zostaje 🙂 Ważne: Porównanie jest niezależnie od wielkości znaków:

getFilteredUsersForText(text) {
  return allUsers.filter(user => user.toLowerCase().includes(text.toLowerCase()))
}

Rezultat

Ostatecznie stworzona aplikacja wygląda tak:

See the Pen React.js w przykładach: Filtrowanie listy by Michał Miszczyszyn (@mmiszy) on CodePen.

Pytania?

Jak wrażenia? Jeśli masz jakiekolwiek pytania albo coś jest niejasne — pisz w komentarzu! To dla mnie cenna informacja zwrotna.

Jeśli chcesz na bieżąco śledzić kolejne części kursu React.js to koniecznie polub mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie: Jak zmieniłby się kod, gdyby filtrowanie użytkowników działo się np. na backendzie i było asynchroniczne? Czy musiałabyś modyfikować komponent  UsersList? Spróbuj to zaimplementować, przyjmując, że funkcja getFilteredUsersForText jest asynchroniczna i ma taką postać:

getFilteredUsersForText(text) {
  return new Promise(resolve => {
    const time = (Math.random() + 1) * 250;
    setTimeout(() => {
      const filteredUsers = allUsers.filter(user => user.toLowerCase().includes(text.toLowerCase()));
      resolve(filteredUsers);
    }, time) ;
  });
}

Ćwiczenie*: Spróbuj szybko wpisać coś w input. Czy zawsze wyświetlają się poprawne dane? Takie problemy to tzw. race conditions. Czy umiesz je tutaj jakoś rozwiązać? Pamiętaj, że React.js to tylko biblioteka do budowania widoków, a wiedza na temat rozwiązywania ogólnych problemów jest uniwersalna i bardzo przydatna z dowolnym frameworkiem 🙂