React + Redux — filtrowanie listy, proste selektory

Ten wpis jest 26 częścią z 39 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami

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? Naucz się React i Redux na naszym szkoleniu!

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.

Nawigacja po kursie:
  1. Wprowadzenie do kursu React.js od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami