Definicja Higher Order Reducer
Higher Order Reducer to funkcja, która zwraca reducer i (opcjonalnie) przyjmuje reducer jako argument.
Brzmi zrozumiale? Proste HOR są… proste ;) Ale koncept jest bardziej rozbudowany niż się może początkowo wydawać, szczególnie jeśli weźmiemy pod uwagę kompozycję!
Zastosowanie
Weźmy sobie jako przykład operowanie na API. Pobieranie danych z endpointa. Musisz obsłużyć takie akcje:
- pobieranie rozpoczęte
- pobieranie zakończone sukcesem (dane)
- błąd pobierania (błąd)
W podstawowej wersji wygląda to tak jak poniżej. Action Creatory:
const dataFetchStarted = () => ({
  type: "FETCH_DATA_STARTED"
});
const dataFetchSucceeded = data => ({
  type: "FETCH_DATA_SUCCESS",
  payload: data
});
const dataFetchErrored = error => ({
  type: "FETCH_DATA_ERROR',
  payload: error
});
I do tego reducer:
const data = (state = { data: null, isLoading: false, error: null }, action) => {
  switch (action) {
    case 'FETCH_DATA_STARTED':
      return { data: null, isLoading: true, error: null };
    case 'FETCH_DATA_SUCCESS':
      return { data: action.payload, isLoading: false, error: null };
    case 'FETCH_DATA_ERROR':
      return { data: null, isLoading: false, error: action.payload };
    default:
      return state;
  }
};
Natomiast sama akcja asynchroniczna (redux-thunk), która pobierze dane wygląda tak:
export const fetchData = () => (dispatch, getState) => {
  dispatch(dataFetchStarted());
  fetch("endpoint")
    .then(res => res.json())
    .then(json => dispatch(dataFetchSucceeded(json.results)));
    .catch(err => dispatch(dataFetchErrored(err)));
};
Problem
A teraz wyobraź sobie, że dane musisz pobierać, w bardzo podobny sposób, z 2, 3, 4… 10 endpointów. Czy to oznacza, że dla każdego z nich musisz zrobić kopiuj, wklej, znajdź i zamień na powyższym kodzie? Słabo…
Ale jest rozwiązanie: Higher Order Reducer
Rozwiązanie
Jak już wspomniałem, higher order reducer to taka funkcja, która zwraca reducer. W ten sposób możemy reducery tworzyć całkowicie dynamicznie. Na przykład tak:
const asyncReducerFactory = (name) => {
  return (state = { data: null, isLoading: false, error: null }, action) => {
    switch (action.type) {
      case `FETCH_${name}_STARTED`:
        return { data: null, isLoading: true, error: null };
      case `FETCH_${name}_SUCCESS`:
        return { data: action.payload, isLoading: false, error: null };
      case `FETCH_${name}_ERROR`:
        return { data: null, isLoading: false, error: action.payload };
      default:
        return state;
    }
  };
};
Jest to funkcja, która przyjmuje tylko nazwę i zwraca reducer. Nazwy akcji są tworzone dynamicznie — na podstawie podanego argumentu name.
Jak jej użyć? Zamiast całego poprzedniego reducera napisałbym teraz tylko:
const data = asyncReducerFactory('DATA');
A jeśli chciałbym stworzyć reducer dla pobierania kontaktów? Nic prostszego:
const contacts = asyncReducerFactory('CONTACTS');
I tak dalej. Kolejne użycia nie wymagają pisania więcej boilerplate'u.
Higher Order Action Creator
No, ale nadal mam sporo boilerplate'u, prawda? Na szczęście action creator też mogę generować dynamicznie:
const asyncActionCreatorFactory = (name, thunk) => () => {
  return (dispatch) => {
    dispatch({ type: `FETCH_${name}_STARTED` });
    return dispatch(thunk)
      .then((data) => data.json())
      .then((json) => dispatch({ type: `FETCH_${name}_SUCCESS`, payload: json }))
      .catch((err) => dispatch({ type: `FETCH_${name}_ERROR`, payload: err }));
  };
};
Ponownie — na podstawie name generuję nazwy akcji. thunk to pewna asynchroniczna akcja, która (zakładam) wywołuje fetch i zwraca Promise.
Jak tego używam?
const fetchContacts = asyncActionCreatorFactory('DATA', (dispatch, getState) => {
  return fetch('endpoint');
});
A dla kontaktów?
const fetchContacts = asyncActionCreatorFactory('CONTACTS', (dispatch, getState) => {
  return fetch('https://randomuser.me/api/?format=json&results=10&seed=' + encodeURIComponent(getState().seed));
});
Tutaj dodatkowo „przemyciłem” parametr pochodzący z getState — tak samo jak we wpisie o redux-thunk.
Podsumowanie
W taki sposób, korzystając z funkcji wyższego rzędu, można uprościć wiele rzeczy w React i Redux. Tworzenie reducerów, action creatorów, a nawet komponentów! Mam nadzieję, że będziesz już potrafiła szybko stworzyć następne akcje i reducery dla kolejnych endpointów Twojego API! zapisz się na szkolenie z React.
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: Zrefaktoruj kod z naszą listą kontaktów tak, aby skorzystać z napisanych w tym wpisie funkcji. Kod znajdziesz tutaj: github.com/typeofweb/typeofweb-kurs-react/tree/contacts-list-4-redux
