Created
December 16, 2016 22:19
-
-
Save benfletcher/c60d7f2838cdba0332bab52a998aa5da to your computer and use it in GitHub Desktop.
Redux Thunk with Fetch - standalone demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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