Last active
July 8, 2019 12:48
-
-
Save tracker1/d6f0bab5634f0f50128c06ecbd134c80 to your computer and use it in GitHub Desktop.
Simple pattern for loading async data with react, redux and redux-thunk middleware
This file contains 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
import api from '../api'; | |
import { ACTION } from './constants'; | |
// action creator for LOADERR | |
export const loadError = error => ({ type: ACTION.LOADERR, payload: error }); | |
export const loading = key => ({ type: ACTION.LOADING, payload: { key } }); | |
export const loaded = (key, data) => ({ type: ACTION.LOADED, payload: { key, data } }); | |
// this action creator assumes that redux-thunk middleware is loaded. | |
export const load = recordId => async (dispatch, getState) => { | |
try { | |
// set loading state | |
dispatch(loading(recordId)); | |
// fetch the actual data | |
const data = await api.getRecord(recordId); | |
// get the currently set loading record in state | |
const ({ loading }) = getState(); | |
// if the record isn't the current one - don't do anything else | |
if (loading !== recordId) return; | |
// set the loaded record to the returned data | |
dispatch(loaded(recordId, data)); | |
} catch (error) { | |
// if an error occurred, log it to the console, and dispatch it out | |
console.error(error); | |
dispatch(loadError(error)); | |
} | |
} |
This file contains 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
export ACTION = { | |
LOADING: 'PREFIX:LOADING', | |
LOADED: 'PREFIX:LOADED', | |
LOADERR: 'PREFIX:LOADERROR', | |
}; | |
export DEFAULT_STATE = { | |
loading: null, | |
loaded: NaN, // never match | |
data: {}, | |
error: null, | |
}; |
This file contains 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
import React, { Component } from 'react'; | |
import { bindActionCreators } from 'redux'; | |
import { connect } from 'react-redux'; | |
import * as Actions from './action'; | |
const mapStateToProps = ({ loaded, loading, error, data }) = ({ loaded, loading, error, data }); | |
const mapDispatchToProps = dispatch => ({ action: bindActionCreators(Actions, dispatch) }); | |
export class MyComponent extends Component { | |
load = _ => this.props.action.load(this.props.id); | |
componentDidMount = _ => this.componentDidUpdate(); | |
componentDidUpdate = _ => { | |
// id passed in as property/attribute | |
const { id, loading, error } = this.props; | |
// there was an error, nothing to do | |
if (error) return; | |
// currently loading or loaded | |
if (id === loading) return; | |
this.load(id); | |
}; | |
render() { | |
const { id, loading, loaded, data, error } = this.props; | |
if (error) return ( | |
<div>Error! <a onClick={this.load}>Retry</a></div> | |
); | |
if (id != loading && loading !== loaded) return ( | |
<div>Loading...</div> | |
) | |
return ( | |
<div>{data.id} Loaded!</div> | |
) | |
} | |
} | |
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent); |
This file contains 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
import clone from 'fclone'; | |
import { ACTION, DEFAULT_STATE } from './constants'; | |
export default (state = clone(DEFAULT_STATE), action) { | |
switch (action.type) { | |
case '@@INIT': // redux initialization - page refresh | |
case '@@router/LOCATION_CHANGE': // connected-react-router | |
// when reloaded/rerouted during a data load, reset this portion of state | |
if (state.loading && state.loading !== state.loaded) { | |
return clone(DEFAULT_STATE); | |
} | |
// NOTE: if this reducer/state section is only valid in certain routes, | |
// you may want to check location.pathname here too. | |
return state; | |
case ACTION.LOADING: | |
return { ...DEFAULT_STATE, loading: action.payload.key }; | |
case ACTION.LOADED: | |
return { ...DEFAULT_STATE, loading: action.payload.key, loaded: action.payload.key, data: action.payload.data }; | |
case ACTION.LOADERROR: | |
return { ...DEFAULT_STATE, error: action.payload; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment