Skip to content

Instantly share code, notes, and snippets.

@js62789
Last active March 7, 2019 15:49
Show Gist options
  • Save js62789/129aec16c6cf2362590ead8daaf927b0 to your computer and use it in GitHub Desktop.
Save js62789/129aec16c6cf2362590ead8daaf927b0 to your computer and use it in GitHub Desktop.
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