Last active
March 7, 2019 15:49
-
-
Save js62789/129aec16c6cf2362590ead8daaf927b0 to your computer and use it in GitHub Desktop.
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 querystring from 'querystring'; | |
import { RSAA } from 'redux-api-middleware'; | |
function queryKey(obj) { | |
const ordered = {}; | |
Object.keys(obj).sort().forEach(function(key) { | |
ordered[key] = obj[key]; | |
}); | |
return querystring.stringify(ordered); | |
} | |
export default class ReduxCollection { | |
static ACTION_TYPE_DELIMITER = '_' | |
static REQUEST_TYPE = { | |
FETCH: 'FETCH', | |
CREATE: 'CREATE', | |
UPDATE: 'UPDATE', | |
REMOVE: 'REMOVE', | |
} | |
static getKey(item) { | |
return item.id; | |
} | |
static getRecord(state, id) { | |
return state.items[id]; | |
} | |
static getRecords(state) { | |
return Object.values(state.items); | |
} | |
static getItem(state, id) { | |
const record = ReduxCollection.getRecord(state, id); | |
return record && record.item; | |
} | |
static getItems(state, query) { | |
if (query) { | |
const key = queryKey(query); | |
const ids = state.queries[key] || []; | |
return ReduxCollection.getItems(state).filter(item => ids.includes(item.id)); | |
} | |
return ReduxCollection.getRecords(state) | |
.filter(record => !!record.lastUpdated) | |
.map(record => record.item); | |
} | |
static isLoading(state) { | |
return state.isLoading; | |
} | |
static isLoadingItem(state, id) { | |
const record = ReduxCollection.getRecord(state, id); | |
return record && record.isLoading; | |
} | |
static generateRecord(item = null) { | |
return { | |
lastUpdated: Date.now(), | |
isLoading: false, | |
item, | |
meta: {}, | |
}; | |
} | |
static updateRecord(state, item) { | |
const defaultRecord = ReduxCollection.generateRecord(item); | |
const record = ReduxCollection.getRecord(state, ReduxCollection.getKey(item)); | |
return { | |
...defaultRecord, | |
...record, | |
lastUpdated: Date.now(), | |
error: false, | |
item, | |
}; | |
} | |
constructor(name) { | |
if (!name) { | |
throw new Error('How are you going to store things without a name?'); | |
} | |
this.name = name.toLowerCase(); | |
this.key = name.toUpperCase(); | |
} | |
getDefaultState() { | |
return { | |
isLoading: false, | |
items: {}, | |
queries: {}, | |
}; | |
} | |
createReducer() { | |
const [ | |
FETCH, | |
FETCH_SUCCESS, | |
FETCH_FAIL, | |
] = this.getRequestEvents(ReduxCollection.REQUEST_TYPE.FETCH); | |
const [ | |
UPDATE, | |
UPDATE_SUCCESS, | |
UPDATE_FAIL, | |
] = this.getRequestEvents(ReduxCollection.REQUEST_TYPE.UPDATE); | |
const { name } = this; | |
const defaultState = this.getDefaultState(); | |
const defaultItem = { | |
lastUpdated: null, | |
isLoading: false, | |
item: null, | |
meta: {}, | |
}; | |
return function reducer(state = defaultState, action = {}) { | |
const { type, meta = {}, payload } = action; | |
switch (type) { | |
case FETCH: | |
if (meta.id) { | |
return { | |
...state, | |
items: { | |
...state.items, | |
[meta.id]: { | |
...(ReduxCollection.getItem(state, meta.id) || defaultItem), | |
isLoading: true, | |
}, | |
}, | |
}; | |
} | |
return { | |
...state, | |
isLoading: true, | |
}; | |
case FETCH_SUCCESS: | |
return { | |
...state, | |
isLoading: false, | |
items: payload[name].reduce((items, item) => ({ | |
...items, | |
[ReduxCollection.getKey(item)]: ReduxCollection.generateRecord(item), | |
}), state.items), | |
queries: { | |
...state.queries, | |
...(meta.query && { [queryKey(meta.query)]: payload[name].map(item => item.id) }), | |
} | |
}; | |
case FETCH_FAIL: | |
return { | |
...state, | |
isLoading: false, | |
error: action.payload, | |
}; | |
case UPDATE_SUCCESS: | |
return { | |
...state, | |
items: payload[name].reduce((items, item) => ({ | |
...items, | |
[ReduxCollection.getKey(item)]: ReduxCollection.updateRecord(state, item), | |
}), state.items), | |
}; | |
default: | |
return { ...state }; | |
} | |
} | |
} | |
getEndpoint(id) { | |
const basePath = `/api/${this.name}`; | |
return id ? `${basePath}/${id}` : basePath; | |
} | |
getRequestEvents(requestType) { | |
// eg. FETCH_THINGS | |
const base = `${requestType}${ReduxCollection.ACTION_TYPE_DELIMITER}${this.key}`; | |
return [base, `${base}_SUCCESS`, `${base}_FAIL`]; | |
} | |
createRequestAction(requestType, id) { | |
return { | |
[RSAA]: { | |
method: 'GET', | |
headers: { 'Content-Type': 'application/json' }, | |
endpoint: this.getEndpoint(id), | |
types: this.getRequestEvents(requestType), | |
meta: { id } | |
}, | |
}; | |
} | |
createFetch(dispatch) { | |
return (id) => { | |
dispatch(this.createRequestAction(ReduxCollection.REQUEST_TYPE.FETCH, id)); | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment