Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Star Wars Autocomplete

Okay, so we have a basic application, but it doesn't do much.

Let's get cooking. We'll start by getting things wired up with Redux Thunk.

import thunk from 'redux-thunk';

// …

const store = createStore(reducer, applyMiddleware(thunk));

Again—I like to start with my actions. We're going to need to hit an API, but let's just start by making sure stuff works.

export const FETCH_CHARACTERS_FULFILLED = 'FETCH_CHARACTERS_FULFILLED';

export const fetchCharacters = (searchTerm) => {
  return {
    type: FETCH_CHARACTERS_FULFILLED,
    payload: { searchTerm },
  };
};

And I'll add a console.log to actions.js for good measure.

const charactersReducer = (characters = [], action) => {
  console.log(action);
  return characters;
};

Alright, now that we have that in place, let's connect FetchCharacters to talk to Redux.

import React, { useState } from 'react';
import { connect } from 'react-redux';
import { fetchCharacters } from './actions';

const FetchCharacters = ({ fetchCharacters }) => {
 // …
};

export default connect(
  null,
  { fetchCharacters },
)(FetchCharacters);

Boom! If I check the console, I can see that the action is firing. Let's turn that action creator into a thunk creator!

import { search } from './api';

export const FETCH_CHARACTERS_FULFILLED = 'FETCH_CHARACTERS_FULFILLED';

export const fetchCharacters = (searchTerm) => {
  return (dispatch) => {
    fetch(search + searchTerm)
      .then((response) => response.json())
      .then((response) => response.results)
      .then((characters) => {
        dispatch({
          type: FETCH_CHARACTERS_FULFILLED,
          payload: { characters },
        });
      });
  };
};

We can now set up that result in the reducer.js.

const charactersReducer = (characters = [], action) => {
  if (action.type === FETCH_CHARACTERS_FULFILLED) {
    return action.payload.characters;
  }

  return characters;
};

Looks like we're getting everything in the console. Now, let's hook it up to the Characters component.

import React from 'react';
import { connect } from 'react-redux';

import Character from './Character';

const Characters = ({ characters = [] }) => {
  return (
    <section className="Characters">
      {characters.map((character) => (
        <Character key={character.id} character={character} />
      ))}
    </section>
  );
};

const mapStateToProps = (state) => {
  return { characters: state.characters };
};

export default connect(mapStateToProps)(Characters);

Now: With Epics

Okay, first—we need to disentangle our actions:

import { search } from './api';

export const FETCH_CHARACTERS = 'FETCH_CHARACTERS';
export const FETCH_CHARACTERS_FULFILLED = 'FETCH_CHARACTERS_FULFILLED';

export const fetchCharacters = (searchTerm) => {
  return {
    type: FETCH_CHARACTERS,
    payload: { searchTerm },
  };
};

export const fetchCharactersFulfilled = (payload) => {
  return {
    type: FETCH_CHARACTERS_FULFILLED,
    payload,
  };
};

Okay, now—let's write out the epic:

import { ajax } from 'rxjs/ajax';
import { ofType } from 'redux-observable';
import { map, mergeMap } from 'rxjs/operators';
import { FETCH_CHARACTERS, fetchCharactersFulfilled } from './actions';

import { search } from './api';

const fetchCharactersEpic = (action$) =>
  action$.pipe(
    ofType(FETCH_CHARACTERS),
    mergeMap((action) =>
      ajax
        .getJSON(search + action.payload.searchTerm)
        .pipe(map((response) => fetchCharactersFulfilled(response.results))),
    ),
  );

export default fetchCharactersEpic;

Alright: let's hook it up:

import { createEpicMiddleware } from 'redux-observable';
import rootEpic from './epic';

// …

const epicMiddleware = createEpicMiddleware();
const store = createStore(reducer, applyMiddleware(epicMiddleware));
epicMiddleware.run(rootEpic);

Cancellation

const fetchCharactersEpic = (action$) =>
  action$.pipe(
    ofType(FETCH_CHARACTERS),
    mergeMap((action) =>
      ajax.getJSON(search + action.payload.searchTerm).pipe(
        map((response) => fetchCharactersFulfilled(response.results)),
        takeUntil(
          action$.pipe(
            tap((value) => console.log('Cancelling', value)),
            ofType(FETCH_CHARACTERS),
          ),
        ),
      ),
    ),
  );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment