import { produce } from 'immer' ; import { JsonapiActionTypes } from './actionTypes' ; import { JsonapiActions } from './actions' ; import { JsonapiState , Collection , EntityRef } from './state' ; import { DataDocument , ResourceObject } from '../../api/models/jsonapi' ; const INITIAL_STATE = { entities : { } , collections : { } , } ; function updatePaginationLinks ( draft , resource ) { if ( resource . links ) { if ( resource . links . next ) { draft . pageInfo = { ... draft . pageInfo , hasNextPage : true , nextPage : resource . links . next , } ; } if ( resource . links . prev ) { draft . pageInfo = { ... draft . pageInfo , hasPrevPage : true , prevPage : resource . links . prev , } ; } } } function addOrUpdateEntities ( draft , receivedAt , resource ) { const { id } = resource ; const { type } = resource ; if ( ! draft . entities [ type ] ) { draft . entities [ type ] = { } ; } draft . entities [ type ] [ id ] = { ... draft . entities [ type ] [ id ] , lastUpdated : receivedAt , isFetching : false , resource , didInvalidate : ! resource . attributes , } ; } function addEntities ( draft , receivedAt , dataDocument ) { if ( Array . isArray ( dataDocument . data ) ) { dataDocument . data . forEach ( ( data ) => addOrUpdateEntities ( draft , receivedAt , data ) ) ; } else { addOrUpdateEntities ( draft , receivedAt , dataDocument . data ) ; } if ( dataDocument . included ) { dataDocument . included . forEach ( ( data ) => addOrUpdateEntities ( draft , receivedAt , data ) ) ; } } function getSelfUrl ( resource ) { if ( resource . links ) { const { self } = resource . links ; if ( typeof self === 'string' ) { return self ; } if ( self && self . href ) { return self . href ; } } return undefined ; } function getEntityRefs ( dataDocument ) { if ( Array . isArray ( dataDocument . data ) ) { return dataDocument . data . map ( ( data ) => ( { type : data . type , id : data . id , self : getSelfUrl ( data ) , } ) ) ; } return [ { type : dataDocument . data . type , id : dataDocument . data . id , self : getSelfUrl ( dataDocument . data ) , } , ] ; } export const jsonapiReducer = ( state = INITIAL_STATE , action ) : JsonapiState => produce ( state , ( draft ) => { switch ( action . type ) { case JsonapiActionTypes . REQUEST_ENTITY : { let typeMap = draft . entities [ action . payload . type ] ; if ( ! typeMap ) { typeMap = { } ; draft . entities [ action . payload . type ] = typeMap ; } const entity = typeMap [ action . payload . id ] ; if ( ! entity ) { typeMap [ action . payload . id ] = { ... action . payload , isFetching : true , lastUpdated : 0 , } ; } else { entity . isFetching = true ; entity . didInvalidate = false ; } } break ; case JsonapiActionTypes . RECEIVED_ENTITY : addEntities ( draft , action . payload . receivedAt , action . payload . dataDocument ) ; break ; case JsonapiActionTypes . RECEIVED_ENTITY_ERROR : { const entity = draft . entities [ action . payload . type ] [ action . payload . id ] ; entity . didInvalidate = true ; entity . isFetching = false ; entity . error = action . payload . error ; } break ; case JsonapiActionTypes . INVALIDATE_ENTITY : if ( draft . entities [ action . payload . type ] && draft . entities [ action . payload . type ] [ action . payload . id ] ) { draft . entities [ action . payload . type ] [ action . payload . id ] . didInvalidate = true ; } break ; case JsonapiActionTypes . REQUEST_COLLECTION : if ( ! draft . collections [ action . payload . url ] ) { draft . collections [ action . payload . url ] = { ... action . payload , isFetching : true , lastUpdated : 0 , totalCount : 0 , } ; } else { draft . collections [ action . payload . url ] = { ... draft . collections [ action . payload . url ] , isFetching : true , didInvalidate : false , } ; } break ; case JsonapiActionTypes . RECEIVED_COLLECTION : { const collection = draft . collections [ action . payload . url ] ; collection . isFetching = false ; collection . entities = getEntityRefs ( action . payload . dataDocument ) ; collection . totalCount = action . payload . dataDocument . meta ? action . payload . dataDocument . meta . totalCount : collection . entities . length ; updatePaginationLinks ( collection , action . payload . dataDocument ) ; addEntities ( draft , action . payload . receivedAt , action . payload . dataDocument ) ; } break ; case JsonapiActionTypes . RECEIVED_COLLECTION_ERROR : { const collection = draft . collections [ action . payload . url ] ; collection . didInvalidate = true ; collection . isFetching = false ; collection . error = action . payload . error ; } break ; case JsonapiActionTypes . INVALIDATE_COLLECTION : { const collection = draft . collections [ action . payload . url ] ; if ( collection ) { collection . didInvalidate = true ; } } break ; case JsonapiActionTypes . REQUEST_NEXT_COLLECTION : draft . collections [ action . payload . url ] . pageInfo . isFetchingNextPage = true ; break ; case JsonapiActionTypes . RECEIVED_NEXT_COLLECTION : { const collection = draft . collections [ action . payload . url ] ; collection . pageInfo . isFetchingNextPage = false ; collection . lastUpdated = action . payload . receivedAt ; collection . entities = [ ... collection . entities , ... getEntityRefs ( action . payload . dataDocument ) , ] ; updatePaginationLinks ( collection , action . payload . dataDocument ) ; addEntities ( draft , action . payload . receivedAt , action . payload . dataDocument ) ; } break ; } return undefined ; } ) ;