React + Redux — filtrowanie listy, proste selektory

Ten wpis jest 26 częścią z 32 w kursie React.js

Jak dokładnie wygląda komunikacja pomiędzy komponentami przy użyciu Redux w React? W tym wpisie pokazuję jak zaimplementować filtrowanie listy przy użyciu Reduksa. Na dokładkę — poznasz pojęcie „selektor” i pewien ważny koncept w Reduksie. Do kodu!

Komponenty a Redux

Komunikowanie ze sobą komponentów, które leżą blisko siebie to bułka z masłem. Do tego bynajmniej nie jest potrzebny Redux 😉 Jeśli nie pamiętasz to odśwież sobie pamięć na temat komunikacji komponentów w React. Dlatego pozwolę sobie wydzielić zupełnie nowy komponent, który za zadanie będzie miał filtrowanie listy. Będzie to po prostu input 😉

W naszej prostej aplikacji może wyglądać, że nie ma to za bardzo sensu, ale wyobraź sobie, że ten komponent leży daleko od samej listy i inny sposób komunikacji pomiędzy nimi nie jest możliwy.

class ContactsFilter extends React.Component {
  render() {
    return (
      <div className="ui icon fluid input">
        <input
          type="text"
          placeholder="Search..."
        />
        <i className="search icon" />
      </div>
    );
  }
}

Oto nasz komponent do filtrowania. Prosty, prawda? Teraz musimy go „wpiąć” w Reduksa.

Akcja do filtrowania

Stwórz nową akcję o nazwie searchContacts i typie SEARCH_CONTACTS:

export const searchContacts = (text) => ({
  type: 'SEARCH_CONTACTS',
  text
});

Ta akcja będzie wysyłana po wpisaniu tekstu w pole do szukania 🙂

Reducer tekstu do szukania

Stwórz nowy reducer o nazwie contactsSearch. Domyślnie wartością ma być pusty string (''), a po akcji SEARCH_CONTACTS powinna się ona zmienić na wpisany ciąg znaków (text):

export const contactsSearch = (state = '', action) => {
  switch (action.type) {
    case 'SEARCH_CONTACTS':
      return action.text;
    default:
      return state
  }
}

Podłączenie akcji do pola

Teraz wystarczy tylko użyć nowego pola w state oraz emitować akcję gdy wartość tekstu się zmienia. Dodaj valueonChange do inputa:

<input
  type="text"
  placeholder="Search..."
  value={this.props.contactsSearch}
  onChange={this.handleSearchChange}
/>

Implementacja metody handleSearchChange to po prostu wywołanie odpowiedniego action creatora z wpisaną wartością:

handleSearchChange = e => {
  this.props.searchContacts(e.currentTarget.value);
};

Jak już pewnie wiesz z poprzedniego wpisu na temat Redux i connect zarówno action creator jak i sama wartość pochodzą z funkcji mapStateToPropsmapDispatchToProps:

import { connect } from "react-redux";
import { searchContacts } from "./actions";

// ……

const mapStateToProps = state => {
  return {
    contactsSearch: state.contactsSearch
  };
};

const mapDispatchToProps = { searchContacts };

export const ContactsFilterContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(ContactsFilter);

Po co selektor?

Teraz cały przepływ danych od inputa do Reduksa i z powrotem do inputa działa prawidłowo. Musisz jeszcze tylko przefiltrować kontakty! Zrób to w funkcji mapStateToPropsApp.jsx:

const mapStateToProps = state => {
  return {
    contacts: state.contacts // o tutaj
  };
};

Implementacja filtrowania nie jest trywialna i będzie to całkiem spory kawałek logiki. Dlatego wyniesiemy ją sobie do osobnego pliku w osobnej funkcji — zwanej selektorem. Po co?

  • nie mieszamy logiki z przepływem danych (mapStateToProps ma być możliwie proste)
  • łatwiej przetestować jednostkowo sam selektor niż mieszankę mapStateToProps z logiką filtrowania
  • do selektorów można dodać cache (memoize), aby przyśpieszyć filtrowanie elementów

Implementacja selektora

Stwórz folder selectors, a w nim plik getFilteredContacts.js. W środku taką funkcję, która jako argumenty przyjmuje kontakty oraz tekst i zwraca przefiltrowaną tablicę kontaktów:

export const getFilteredContacts = (contacts, text) => {
  const contactsSearch = text.toLowerCase();

  return contacts.filter(contact => {
    const { first, last } = contact.name;

    return (
      first.toLowerCase().includes(contactsSearch) ||
      last.toLowerCase().includes(contactsSearch)
    );
  });
};

Filtrujemy tylko po imieniu i nazwisku; wielkość znaków jest ignorowana. Teraz już wystarczy, że tylko zmodyfikujesz App.jsx:

const mapStateToProps = state => {
  return {
    contacts: getFilteredContacts(state.contacts, state.contactsSearch)
  };
};

Efekt

Zobacz sam(a):

Kod znajdziesz jak zwykle na moim GitHubie: github.com/mmiszy/typeofweb-kurs-react/tree/contacts-list-3-redux

Podsumowanie

Płynnie poruszasz się po Reduksie 😉 Jak Ci się podoba do tej pory?

Jak filtrowanie listy w Reduksie wypada w porównaniu do zaimplementowanego wcześniej filtrowania statycznej listy? Pewnie wygląda Ci to na mnóstwo niepotrzebnego zachodu. Ale warto zauważyć, że teraz input do wyszukiwania mógłby się znaleźć w dowolnym miejscu aplikacji, a tekst do wyszukania nie musi przechodzić w górę i w dół przez kolejne komponenty, żeby trafić do listy. Dodatkowo zyskałaś/eś możliwość filtrowania listy na różne sposoby — np. teraz trywialne byłoby dodanie przycisku „Znajdź wszystkich o imieniu Magda”.

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie: Dodaj do selektora cache (memoize). Możesz skorzystać z biblioteki reselect.