Skip to content

Instantly share code, notes, and snippets.

@josefdlange
Created September 21, 2016 21:04
Show Gist options
  • Select an option

  • Save josefdlange/dd68023971e817432976d15ed1e89261 to your computer and use it in GitHub Desktop.

Select an option

Save josefdlange/dd68023971e817432976d15ed1e89261 to your computer and use it in GitHub Desktop.
Redux REST Example
// Actions
var startLoadingX = function() {
return {
type: START_LOADING_X // Define this above as you do with Actions.
}
}
var completeLoadingX = function(response, error) {
return {
type: COMPLETE_LOADING_X, // Ditto.
response,
error
}
}
var fetchX = function(possibleParameters) {
return dispatch => {
dispatch(startLoadingX())
myApiClientIWroteThatReturnsPromisesForApiCalls.getStuff(possibleParameters)
.then(response => dispatch(completeLoadingX(response, null)))
.catch(error => dispatch(completeLoadingX(null, error)))
}
}
// Reducer
const initialState = {
isLoadingX: false,
X: {}, // or [] I suppose, depending on the shape you're expecting your response to have!
errorLoadingX: false,
XErrorData: null
}
export default function myStoresReducer(state = initialState, action) {
switch(action.type) {
case START_LOADING_X:
return Object.assign(state, {}, {
isLoadingX: true,
errorLoadingX: false,
X: {}, // you can decide if you want to clear the actual state on every load. For the sake of UI sometimes it's nice to keep old data there to show anything but a blank component.
XErrorData: null
})
case COMPLETE_LOADING_X:
if(action.error != null) {
return Object.assign(state, {}, {
isLoadingX: false,
errorLoadingX: true,
X: {}, // Or null or you can leave it alone as discussed above.
XErrorData: action.error
})
} else {
return Object.assign(state, {}, {
isLoadingX: false,
errorLoadingX: false,
X: state.response, // If your data is embedded in a generic response data structure, this would be a good place to unwrap it, or even in the API client itself.
XErrorData: null
})
}
}
}
@benz2012

benz2012 commented Sep 21, 2016

Copy link
Copy Markdown

Haha I use thunk and axios as well!

So lets say the data being returned is something like:

[
    {id: 1, item: 'Shirt', tags: []},
    {id: 2, item: 'Pants', tags: []}
]

Then you have a react component with functionality to add a tag to an element. And a complementary reducer to update the Redux store on that action.

case ADD_TAG:
    return {
        ...state, 
        tags: [...state, action.payload]
    }

Would that react component be dispatching multiple actions to keep the backend in sync with the Redux store?

addTag() {
    this.props.dispatch(addTagAction());
    this.props.dispatch(postX());
    this.props.dispatch(fetchX());
}

And I realize you would want the axios.post to chain with .then() to a subsequent axios.get to eliminate race conditions, but I wrote it like that to demonstrate my question properly.

@josefdlange

Copy link
Copy Markdown
Author

A well-implemented RESTful API will return the updated model on response, so you'd probably have in your reducer for receiving the response some code to create a new version of the state with an almost exact copy of the prior state -- except the new model data overwriting the old local version in that array. For example:

return Object.map(state, {}, {
    X: state.X.map(obj => obj.id == newObj.id ? newObj : obj)
})

@benz2012

Copy link
Copy Markdown

Wow, I'm such an idiot. Of course POST requests return the the updated model.

So you would only need one action handler -> that would dispatch a multiple-action thunk function -> which would cause the Redux store to be updated twice?

Once when the reducer catches the initial action, and again when(if) the response returns? But because the data should be identical there would be no change to the user because the React Virtual DOM will not notice any changes?

So for example:

// React Component
incrementValue() {
    this.props.dispatch(incrementValueAction(this.props.value + 1)); // value received from store via connect
}
render() {
    <button onClick={this.incrementValue.bind(this)}>Increment Value</button>
}

// Action Handler
export default incrementValueAction(value) {
    return dispatch => {
        dispatch( {type: INC_START, payload: value} )
        axios.post("rest/url/", {value: value})
            .then(dispatch( {type: INC_COMPLETE, payload: response.data} ))
            .catch(...)
    }
}

// Reducer
switch (action.type) {
    case INC_START:
        return {...state, value: action.payload}
    case INC_COMPLETE:
        return {...state, value: action.payload}
}

So then the question becomes, is it really worth having TWO reducer cases that update the Redux state to the same previous state if the RESTful database is always in sync?

@josefdlange

Copy link
Copy Markdown
Author

You don't even need the first private action (of the two inside the thunk) unless you want to be keeping track of whether or not you are loading --- so that your UI can display that and/or if you want/need to do some cleanup before receiving a potential response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment