React + Redux + normalizr api request error handling

934 Views Asked by At

I have implemented an abstract "entity" reducer for every single API call from my app. Now I would like to use normalizr, to keep API data in normalized shape. Until now I was saving my API request response, that came from API middleware by API actions, in reducer like:

case action.type.includes(API_ERROR):
case action.type.includes(API_SUCCESS):
  return {
    ...state,
    [action.payload.label]: {
      error: action.isError,
      data: action.payload.data
    }
  };

to keep my Error flag to be able to indicate (in Components) if the request did succeed or not.

If I do understand normalizr correctly, then entities reducer will be treated like "database". I suppose that having error or isError flag in normalized data is not right is it?.

I'm just not able to solve, how my Reducer will look when I use normalizr to normalize data and how I will be able to track (In components...) if the request succeeds or not after this code refactoring

The other thing is that if my request fails, response data will no contain ID or any other identification which tells what request did fail (I'm currently using Entity label from, e.g., fetchMembers() action as the indicator for that). Failed response data contains only status and reason (why).

Do you guys have any suggestions, please? Can I keep using only single abstract reducer for that, or I should better make custom reducers for all of my API actions?


SUBQUESTION:

And I'm also having requests, which response data are not significant in context of components and only significant info is if request SUCCED or FAILED (requests like log out, log in, ...). I'm assuming that their responses should not be assigned to the redux store to prevent bloating of the store. What can I do with requests like these, please?

1

There are 1 best solutions below

3
On

Don't put isError or isLoading flags in normalized data because normalizr is for normalizing deeply nested objects. It essentially, takes a deeply nested javascript object and flattens it out and in this way you can get rid of redundant data which is its biggest advantage. We used React + Redux-Sagas + normalizr. Hope this fiddle of code will help you out in setting up your reducer

Reducer

// @flow

import {
  FETCH_DETAILED_EXPERIENCE_DATA,
  FETCH_DETAILED_EXPERIENCE_DATA_SUCCESS,
  FETCH_DETAILED_EXPERIENCE_DATA_ERROR,
} from '../ActionTypes'
import Immutable from 'seamless-immutable'
import { createReducer } from '../CreateReducer'

const INITIAL_STATE = Immutable({
  experienceData: null,
  isFetching: false,
  error: null,
})

const reducers = {
  [FETCH_DETAILED_EXPERIENCE_DATA]: (state, action) => {
    return Immutable.merge(state, { experienceData: null, isFetching: true })
  },
  [FETCH_DETAILED_EXPERIENCE_DATA_SUCCESS]: (state, { data }) => {
    return Immutable.merge(state, { experienceData: data, isFetching: false, error: null })
  },
  [FETCH_DETAILED_EXPERIENCE_DATA_ERROR]: (state, { error }) => {
    return Immutable.merge(state, { error, isFetching: false })
  },
}

export const reducer = createReducer(INITIAL_STATE, reducers)

Sagas

import { FETCH_DETAILED_EXPERIENCE_DATA } from '../ActionTypes'
import { put, takeLatest, call } from 'redux-saga/effects'
import API from '../../Services/baseApi'
import * as ExperienceActions from './Actions'
import * as EntitiesActions from '../entities/Actions'

import parseExperienceDetailResponse from 'App/Schemas/APIResponse/ExperienceDetailResponse'

function* fetchExperienceDetails(action) {
  try {
    const response = yield call(API.get, 'URL')
    const { entities, result } = parseExperienceDetailResponse(response.data)
    yield put(EntitiesActions.updateEntities(entities))
    yield put(ExperienceActions.fetchDetailedExperienceSuccess(result))
  } catch (e) {
    yield put(ExperienceActions.fetchDetailedExperienceError(e.message))
  }
}

export default function* root() {
  yield [yield takeLatest(FETCH_DETAILED_EXPERIENCE_DATA, fetchExperienceDetails)]

Schema

// @flow

import { normalize, schema } from 'normalizr'

const experience = new schema.Entity(
  'experiences',
  {
    owner: user,
  },
  { idAttribute: 'Id' },
)


const response = new schema.Object({
  experience,
})

export default (input: any) => {
  const { entities, result } = normalize(input, response)

  return {
    entities,
    result: result,
  }
}

Hope this helps thanks.