Created
October 11, 2017 11:09
-
-
Save Bitaru/5e91fc2ae278d278189f3a3d21ed629e to your computer and use it in GitHub Desktop.
redux-shelf
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 { createAction } from 'redux-actions'; | |
import { identity, isArray } from 'lodash'; | |
import { SubmissionError } from 'redux-form'; | |
export const PREFIX = '@@shelf'; | |
export const statuses = ['START', 'SUCCESS', 'ERROR']; | |
const getType = (status:string) => (name):string => `${PREFIX}/${status} -> ${name.toString()}` | |
const createApiAction = (name, status) => createAction( | |
getType(status)(name), | |
identity, | |
() => ({ | |
name, | |
status, | |
time: new Date().getTime() | |
}) | |
); | |
const defaultMapper = data => isArray(data) ? ({ entities: data }) : ({ entity: data }); | |
export const create = (actionName, apiFunction, responseMapper = defaultMapper) => { | |
const action = createAction(actionName, responseMapper); | |
const [start, success, error] = statuses.map(status => createApiAction(actionName, status)); | |
const apiAction = (...params) => async dispatch => { | |
dispatch(start()); | |
try { | |
const { data } = await apiFunction(...params); | |
dispatch(success(data)); | |
dispatch(action(data)); | |
return data; | |
} catch (e) { | |
console.error(e); | |
dispatch(error(e)); | |
throw new SubmissionError({ _error: e }); | |
} | |
} | |
apiAction.toString = ():string => actionName; | |
return apiAction; | |
}; | |
export const success = getType(statuses[1]); | |
export const error = getType(statuses[2]); |
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 axios from 'axios'; | |
import * as jsonp from 'jsonp'; | |
import session from 'lib/session'; | |
import { isFunction } from 'lodash'; | |
import * as config from 'config'; | |
import { create } from './actions'; | |
import { stringify } from 'qs'; | |
const jsonpRequest = (path, query, params) => new Promise((resolve, reject) => | |
jsonp( | |
`${config.searchApi.url}/v3/${path}${query && `?${stringify(query)}` || ''}`, | |
params, | |
(err, data) => err && reject(err) || resolve({ data })) | |
); | |
const createUrl = (strings, keys) => strings.map((s, i) => | |
keys[i] | |
? s + (isFunction(keys[i]) ? keys[i]() : keys[i]) | |
: s | |
).join(''); | |
const replaceParams = function(pattern, params) { | |
return pattern.replace(/:[a-z|A-Z]+/g, match => { | |
const matchedParam = match.substr(1); | |
const value = params[matchedParam]; | |
if (typeof value === 'undefined') { | |
throw new Error(`Matched param "${matchedParam}" is not presented at given object`); | |
} | |
return value; | |
}); | |
} | |
const instance = axios.create({ | |
baseURL: `${config.adminApi.url}/${config.adminApi.version}`, | |
}); | |
const createApiCall = (method:string, strings:string[], keys: any[]) => (data, params) => { | |
const normalizedUrl: string = replaceParams(createUrl(strings, keys), {...data, ...params}); | |
if (method === 'json') { | |
const user = {uid: 1, sid:1}; | |
const t_client = (new Date).getTime(); | |
const log = false; | |
const normalizeData = {...(data && data || {}), log, user, t_client }; | |
return jsonpRequest(normalizedUrl, normalizeData, params); | |
} | |
return instance.request({ | |
url: session.merchant && !normalizedUrl.startsWith('/') ? `merchants/${session.getMerchant()}/${normalizedUrl}` : normalizedUrl, | |
headers: { | |
[config.adminApi.token]: session.token | |
}, | |
method, | |
data, | |
params | |
}); | |
} | |
const createRequest = method => (strings, ...keys) => (responseMapper?) => { | |
const path = strings.join(''); | |
return create(`[${method}]${path}`, createApiCall(method, strings, keys), responseMapper); | |
} | |
export const get = createRequest('get'); | |
export const put = createRequest('put'); | |
export const post = createRequest('post'); | |
export const del = createRequest('delete'); | |
export const json = createRequest('json'); |
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 { Component } from "react"; | |
import { createEagerFactory, createEagerElement } from 'recompose'; | |
import { isArray, isFunction } from 'lodash'; | |
import Spinner from 'components/Spinner'; | |
const createPromise = (handler, props) => { | |
if (!props[handler]) { | |
console.warn(`Property [${handler}] not found`); | |
return false; | |
} | |
return props[handler](); | |
}; | |
export const asyncConnect = (...handlers) => BaseComponent => { | |
const factory = createEagerFactory(BaseComponent); | |
return class Connector extends Component<any, any>{ | |
state = { | |
loaded: false, | |
hasError: false, | |
error: void 0, | |
} | |
componentWillMount() { | |
Promise.all( | |
handlers.map((handler) => isFunction(handler) | |
? handler(this.props) | |
: createPromise(handler, this.props) | |
) | |
) | |
.then(() => this.setState({ loaded: true })) | |
.catch(e => { | |
console.log(e); | |
return this.setState({ loaded: true, hasError: true, error: e }) | |
}); | |
} | |
render() { | |
return this.state.loaded | |
? factory({ ...this.state, ...this.props }) | |
: createEagerElement(Spinner); | |
} | |
} | |
} |
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
export const field = name => data => ({ [name]: data }); |
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
export * from './actions'; | |
export * from './selectors'; | |
export * from './helpers'; | |
export { createReducer } from './reducer'; | |
export { asyncConnect } from './asyncConnect'; |
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 { handleActions, combineActions } from 'redux-actions'; | |
import { isArray } from 'lodash'; | |
import { PREFIX, statuses } from './actions'; | |
const applyState = (s, { payload }) => ({ ...s, ...payload }); | |
const emptyObject = {}; | |
const emptyArray = {}; | |
export const createReducer = ( | |
actions?: any, | |
reducer = emptyObject, | |
initialState = emptyObject | |
) => handleActions({ | |
...(actions && { | |
[combineActions( | |
...(isArray(actions) ? actions : Object.keys(actions).map(k => actions[k])) | |
)]: applyState | |
}), | |
...reducer | |
}, initialState); | |
export const reducer = (state = {}, { type, meta, payload }) => { | |
if (!type.includes(PREFIX)) return state; | |
return { | |
...state, | |
[meta.name]: { | |
...meta, | |
payload | |
} | |
} | |
} |
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 { createSelector } from 'reselect'; | |
import { statuses } from './actions'; | |
import { path, identity } from 'ramda'; | |
const [ start, success, error ] = statuses; | |
const getStatus = name => s => s.shelf[name] && s.shelf[name].status; | |
export const isFetching = action => { | |
const name = action.toString(); | |
return createSelector( | |
getStatus(name), | |
status => status === start | |
); | |
}; | |
export const hasError = action => { | |
const name = action.toString(); | |
return createSelector( | |
getStatus(name), | |
status => status && status === error | |
); | |
}; | |
export const getList = reducerName => createSelector( | |
s => s[reducerName].entities, | |
list => list | |
); | |
export const getItem = reducerName => { | |
const [name, field] = reducerName.split('.'); | |
return createSelector( | |
s => s[name][field || 'entity'], | |
item => item | |
) | |
}; | |
export const get = (...deep) => createSelector(path(deep), identity); | |
export const routeParams = createSelector( | |
s => s.match && s.match.params, | |
params => params | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment