-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API calls that dispatch multiple actions for keeping API response data and other stuff (like metadata, etc.) in separate parts of the store? (With sample code) #65
Comments
Hey this is really great! I'm just getting my feet wet with this library, but I'm going to apply what you have here to my own project and see if there's anything I would change :) |
Hey @anyong! The Are you explicitly listing all of your models there as your root reducer or do you handle that somewhere else too? Thanks! |
The models are just the |
Thanks for the quick reply! Gotcha. Just so I understand, it looks like you're getting rid of having to use The reason why I'm asking is because I have a series of reducers that I use with export default combineReducers({
router,
form,
users,
vendors,
// etc..
}) The approach you're taking is a bit different, because the API reducer will handle everything on its own and normalize is actually defining the store at a top level, right? Not nested within a reducer? Does that make sense? Thanks |
The top-level {
entities: {
modelA: { 1: { .... }, 2: { ... } }
},
modelAListView: {
active: [1, 2]
}
} Make sense? |
Hey @anyong Just wanted to let you know this all makes sense. I took a look through the real-world example and could match the ideas. Thank you for helping me out! |
Hey @anyong , After playing around with this for a few days. Here are some pain points: CRUD. This technique seems to be working great when you only have to fetch stuff. When you try to do much more than that, it gets a little complicated, especially with |
Ok, sorry for the lack of context. Did a ton of refactoring on my end. For anyone else interested, this can DEFINITELY be cleaned up, but I just needed something that works for now. Background: Pure CRUD Api: DELETE: returns no response body, 204 import {
CALL_API,
getJSON,
ApiError
} from 'redux-api-middleware'
import {normalize} from 'normalizr'
import {omit, values} from 'lodash'
const HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
const CREDENTIALS = 'same-origin'
export const API_REQUEST = 'API_REQUEST'
export const API_SUCCESS = 'API_SUCCESS'
export const API_FAILURE = 'API_FAILURE'
const API_ROOT = '/api'
export default function ({
method = 'GET',
body,
headers = HEADERS,
credentials = CREDENTIALS,
url,
actionTypes,
schema,
...rest
}) {
const endpoint = `${API_ROOT}/${url}`
let apiAction
const config = {}
config.method = method
config.endpoint = endpoint
config.headers = headers
config.credentials = credentials
if (body) config.body = body
return dispatch => {
if (Array.isArray(actionTypes) && actionTypes.length === 3) {
apiAction = {
[CALL_API]: {
...config,
types: [
{
type: actionTypes[0],
payload: () => dispatch({type: API_REQUEST})
},
{
type: actionTypes[1],
payload: (action, state, res) => onSuccess(dispatch, res, schema, method, url, state)
},
{
type: actionTypes[2],
payload: (action, state, res) => onFailure(dispatch, res, method, url, state)
}
]
}
}
} else {
apiAction = {
[CALL_API]: {
...config,
...rest
}
}
}
return dispatch(apiAction)
}
}
function handleNormalResponse (dispatch, res, schema, json) {
let data
if (Object.keys(json)[0] === schema.key) {
data = normalize(json, {[schema.key]: schema.type})
} else {
data = normalize(json, schema.type)
}
let ids = {}
if (typeof data.result === 'string') {
ids = data.result
} else {
Object.keys(data.entities).map(entity => {
ids[entity] = Object.keys(data.entities[entity]).map(id => id)
})
}
dispatch({
type: API_SUCCESS,
payload: {
entities: data.entities,
ids
}
})
return {
ids
}
}
function handleDeleteResponse (dispatch, schema, url, state) {
const id = url.split('/').pop()
const ids = state[schema.key].ids
const items = state.entities[schema.key]
const toKeep = ids.filter(key => key !== id)
const itemsToKeep = omit(items, id)
dispatch({
type: API_SUCCESS,
payload: {
entities: {
[schema.key]: itemsToKeep
},
ids: toKeep
}
})
return {
ids: toKeep,
deleted: id
}
}
function onSuccess (dispatch, res, schema, method, url, state) {
return getJSON(res)
.then(json => {
switch (method) {
case 'DELETE':
return handleDeleteResponse(dispatch, schema, url, state)
default:
return handleNormalResponse(dispatch, res, schema, json)
}
})
}
function onFailure (dispatch, res, state, method) {
return getJSON(res)
.then(json => {
const payload = new ApiError(res.status, res.statusText, json)
if (payload.status === 401) {
window.location = '/'
}
dispatch({
type: API_FAILURE,
payload
})
return payload
})
} When you fire something off, you'll two things dispatch (thanks to thunks):
(and requests/failures go along with it) This is still going through a bit of a refactor, but: [API_SUCCESS]: (state, action) => {
if (action.payload && action.payload.entities) {
// whether you update, delete or GET, it'll always return the new stuff you want to replace!
}
} |
@peterpme |
:) |
hi @peterpme
Thank you |
So this was an edge case I was dealing with. The server would sometimes respond with a keyed payload and sometimes it would return the payload:
or
Normalizr didn't like that, so I tweaked it a bit. |
hi @peterpme , Sorry, I go again. I have a question that I want to be like real-world as
But we would like to achieve this in this example
There is no better way。 |
you will have to write your own call-api middleware for that unless this middleware supports it. It's relatively easy and http://redux.js.org/ actually has one for you to use :) |
@peterpme This means that it's also not possible to listen for a promise? |
@Gamemaniak whether you write your own custom api middleware or use this one, you can return a promise. I'm not sure what you mean by listening for a promise |
I'm working on what is basically a CMS and has about ~30 or so different model classes that are rather inter-related. The easiest way to work with them is definitely with
normalizr
as suggested. Now, for my redux store object, I'm thinking the best way to set things up will be:So, my question is, in order to get this to happen with
redux-api-middleware
, I need a reducer for thestate.entities
(theapiReducer
), and then my individual reducers as normal for all of the different views and such.But then, I have to dispatch two separate actions to make sure that (A) the
apiReducer
receives updates whenever the API gets called, and (B) the appropriate model reducer receives updates when the API call involves that particular model.I have worked out a solution to do this using
redux-thunk
, but I would really appreciate any feedback on this approach. So far it's working very nicely, and means my actual API calls are super simple to make from within my redux action creators. I would love to know if there is a better way anyone else has come up with!So, first, here's my helper utility to make API requests with secondary action effects:
Now the next one is super simple, to update the
entities
branch of the store:Now calling the API from any action is as easy as:
and I will have access to all of the data I need in reducers that handle both API actions and MODEL_A related actions.
Comments/feedback/suggestions? Thanks!
The text was updated successfully, but these errors were encountered: