Skip to content

Instantly share code, notes, and snippets.

@edtoken
Forked from Bitaru/actions.js
Created October 11, 2017 11:14
Show Gist options
  • Save edtoken/34f5f060a56678cf5443a6bcf5282afc to your computer and use it in GitHub Desktop.
Save edtoken/34f5f060a56678cf5443a6bcf5282afc to your computer and use it in GitHub Desktop.
redux-shelf
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]);
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');
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);
}
}
}
export const field = name => data => ({ [name]: data });
export * from './actions';
export * from './selectors';
export * from './helpers';
export { createReducer } from './reducer';
export { asyncConnect } from './asyncConnect';
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
}
}
}
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