Skip to content

Instantly share code, notes, and snippets.

@scaleflake
Last active March 4, 2020 12:08
Show Gist options
  • Save scaleflake/add29e1664fd9c73f1d7922dc3d7afa1 to your computer and use it in GitHub Desktop.
Save scaleflake/add29e1664fd9c73f1d7922dc3d7afa1 to your computer and use it in GitHub Desktop.
/// rpc.js
import { createAction } from 'redux-actions';
import { put, call, takeEvery } from 'redux-saga/effects';
export function buildRpcMethod(methodName, { customSelectors, customSagas }) {
const doRequest = (params) => {
const { data: { result, error } } = await axios.post('/rpc', {
jsonrpc: '2.0',
method: methodName,
params,
id: 1,
});
if (error) {
throw new Error(error.message);
} else {
return result;
}
}
const actions = {
request: createAction(`${methodName}/REQUEST`),
success: createAction(`${methodName}/SUCCESS`),
failure: createAction(`${methodName}/FAILURE`),
};
const sagas = {
* doRequest(action) {
try {
const result = yield call(doRequest, action.payload);
yield put(actions.success(result));
} catch (e) {
yield put(actions.failure(e.message));
}
},
* final() {
yield takeEvery(actions.request, sagas.doRequest);
}
...customSagas,
};
return {
methodName,
actions,
sagas,
reducer: combineReducers({
data: handleActions({
[actions.success]: (state, action) => action.payload,
}, null),
error: handleActions({
[actions.failure]: (state, action) => action.payload,
}),
isLoading: handleActions({
[actions.request]: () => true,
[actions.success]: () => false,
[actions.failure]: () => false,
}, false),
}),
selectors: {
data: (state) => state[methodName]?.data,
error: (state) => state[methodName]?.error,
isLoading: (state) => state[methodName]?.isLoading,
...customSelectors,
},
};
}
export const rpcMethods = {};
for (const methodName of [`products.getChunk`, `shops.getChunk`]) {
rpcMethods[methodName] = buildRpcMethod(methodName);
}
export function useRpc(dispatch, method) {
const { selectors, actions } = rpcMethods[method];
const data = useSelector(selectors.data);
const error = useSelector(selectors.error);
const isLoading = useSelector(selectors.isLoading);
useEffect(() => {
if (!data) {
dispatch(actions.request);
}
}, [dispatch, data]);
return [data, error, isLoading];
}
export function combineRpcs(...rpcs) {
return [
...rpcs.map(r => r[0]),
rpcs.find(r => r[1])[1],
rpcs.some(r => r[2]),
];
}
/// store.js
import { rpcMethods } from '@/rpc';
export default combineReducers({
someModule1,
someModule2,
someModuleN,
rpc: combineReducers(Object.fromEntries(Object.entries(rpcMethods).map(r => [
r.methodName,
r.reducer,
]))),
})
/// App.jsx
import { useRpc, combineRpcs } from '@/rpc';
function App() {
const dispatch = useDispatch();
const [products, shops, rpc3result, error, isLoading] = combineRpcs(
useRpc(dispatch, "products.getChunk"),
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),z
useRpc(dispatch, "shops.getChunk"),
);
if (idLoading) {
return <LoadingSpinner />;
} else if (error) {
return <Error error={error} />;
} else {
return <ShopsAndProducts products={products} shops={shops} />;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment