Skip to content

Instantly share code, notes, and snippets.

@benfletcher
Created December 16, 2016 22:19
Show Gist options
  • Save benfletcher/c60d7f2838cdba0332bab52a998aa5da to your computer and use it in GitHub Desktop.
Save benfletcher/c60d7f2838cdba0332bab52a998aa5da to your computer and use it in GitHub Desktop.
Redux Thunk with Fetch - standalone demo
require('babel-polyfill');
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk'
import {Provider, connect} from 'react-redux';
/************ ACTION CONSTANTS AND ACTION CREATORS ************/
const ADD_REPOSITORY = 'ADD_REPOSITORY';
const addRepository = repository => ({type: ADD_REPOSITORY, repository});
const FETCH_DESCRIPTION_SUCCESS = 'FETCH_DESCRIPTION_SUCCESS';
const fetchDescriptionSuccess = (repository, description) => ({
type: FETCH_DESCRIPTION_SUCCESS,
repository,
description
});
const FETCH_DESCRIPTION_ERROR= 'FETCH_DESCRIPTION_ERROR';
const fetchDescriptionError = (repository, error) => ({
type: FETCH_DESCRIPTION_ERROR,
repository,
error
});
/************ REDUCERS AND INITIAL STATE ************/
const initialRepositoryState = [];
const repositoryReducer = (state = initialRepositoryState, action) => {
if (action.type === ADD_REPOSITORY) {
return [
...state, {
name: action.repository,
rating: null
}];
} else if (action.type === FETCH_DESCRIPTION_SUCCESS) {
// Find the index of the matching repository
const index = state.findIndex(repository =>
repository.name === action.repository
);
if (index === -1) {
throw new Error('Could not find repository');
}
const before = state.slice(0, index);
const after = state.slice(index + 1);
const newRepository = Object.assign({}, state[index], {
description: action.description
});
return [...before, newRepository, ...after];
} else if (action.type === FETCH_DESCRIPTION_ERROR) {
// Find the index of the matching repository
const index = state.findIndex(repository =>
repository.name === action.repository
);
if (index === -1) {
throw new Error('Could not find repository');
}
const before = state.slice(0, index);
const after = state.slice(index + 1);
const newRepository = Object.assign({}, state[index], {
description: 'N/A'
});
return [...before, newRepository, ...after];
}
return state;
}
const fetchDescription = repository => dispatch => {
const url = `https://api.github.com/repos/${repository}`;
return fetch(url).then(response => {
if (!response.ok) {
const error = new Error(response.statusText)
error.response = response
throw error;
}
return response.json();
})
.then(data =>
dispatch(fetchDescriptionSuccess(repository, data.description))
)
.catch(error =>
dispatch(fetchDescriptionError(repository, error))
);
};
/************ CREATE STORE ************/
// Let's initialize the sotre with some data, just for demo purposes
let initialState = [{
name: 'octocat/Hello-World',
rating: 5
}];
// createStore without redux dev tools
// const store = createStore(repositoryReducer, initialState, applyMiddleware(thunk));
// createStore with redux dev tools. Note the addition of compose that needs to be imported above
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(repositoryReducer, initialState, composeEnhancers(
applyMiddleware(thunk)
));
/************ DEMO AND LOGGING ************/
// subscribe to any change in the store and log out the change
// note: this can also be viewed in Redux DevTools
store.subscribe(() => {
console.log('updated', store.getState());
})
// MANUAL TEST OF DISPATCH AND GETSTATE
// fetchDescription of item already in state
store.dispatch(fetchDescription('octocat/Hello-World'));
// addRepository item to state
store.dispatch(addRepository('octocat/Spoon-Knife'));
// fetchDescription of new item
store.dispatch(fetchDescription('octocat/Spoon-Knife'));
/************ BREAK IT DOWN ************/
// Break it down, down, down, down...
// So addRepository returns a normal action object
let addRepoActionObject = addRepository('octocat/git-consortium')
console.log('addRepoActionObject', addRepoActionObject);
store.dispatch(addRepoActionObject);
// But fetchDescription returns a curry function that accepts 'dispatch' like, `function (dispatch) {/*...*/}`
// REF: What is a curry? http://stackoverflow.com/a/36321
let fetchDescriptionCurry = fetchDescription('octocat/git-consortium');
// SideNote: fetchDescriptionCurry now very is specific to 'octocat/git-consortium'
// since the repository 'octocat/git-consortium' is captured in a closure
// So we can call the function and pass in store.dispatch as a callback
// Fetch will call dispatch once the API call returns
fetchDescriptionCurry(store.dispatch);
// Break it down, get up, Say what? :-)
console.log(store.getState()); // Logs [{ name: 'joe', rating: null}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment