Skip to content

Instantly share code, notes, and snippets.

@geophree
Created August 22, 2016 18:17
Show Gist options
  • Save geophree/bb438d97b243d4a8c185ede91075d5ea to your computer and use it in GitHub Desktop.
Save geophree/bb438d97b243d4a8c185ede91075d5ea to your computer and use it in GitHub Desktop.
Universal redux-saga loading module WIP
import R from 'ramda';
export const LOADING_START = 'app-resource/loading/LOADING_START';
export const LOADING_DONE = 'app-resource/loading/LOADING_DONE';
export const LOADING_ERROR = 'app-resource/loading/LOADING_ERROR';
const defaultState = Object.freeze({
loading: [],
error: [],
});
export default function loadingReducer(state = defaultState, action) {
const { type, data, err } = action;
switch (type) {
case LOADING_START:
console.log('loading start');
return Object.assign({}, state, {loading: R.append(data, state.loading)});
case LOADING_DONE:
console.log('loading done');
const without = R.without([data]);
const oneLess = R.compose(R.tail, R.filter(R.equals(data)));
const removeOne = R.converge(R.concat, [without, oneLess]);
return Object.assign({}, state, {loading: removeOne(state.loading)});
case LOADING_ERROR:
console.log('loading error');
return Object.assign({}, state, {error: R.append([data, err], state.error)});
default:
return state;
}
}
export const loadingStart = (data) => ({type: LOADING_START, data});
export const loadingDone = (data) => ({type: LOADING_DONE, data});
export const loadingError = (data, err) => ({type: LOADING_ERROR, data, err});
export const isLoadingSelector = ({loading: {loading}}) => loading.length !== 0;
export const errorSelector = ({loading: {error}}) => error;
import {LOADING_DONE, loadingStart, loadingDone, loadingError, isLoadingSelector, errorSelector} from './Loading';
import {call, put, select, take} from 'redux-saga/effects';
export const addLoading = (func) => {
return function* addLoadingSaga(...args) {
console.log('add loading saga');
const type = 'TBD';
const data = [type, func, ...args];
yield put(loadingStart(data));
console.log('loading started');
try {
yield call(func, ...args);
console.log('loaded call done');
} catch (err) {
yield put(loadingError(data, err));
console.log('loading errored ');
} finally {
yield put(loadingDone(data));
console.log('loading done');
}
};
};
export function* waitForLoadingComplete() {
console.log('A1');
while (yield select(isLoadingSelector)) {
console.log('A2');
console.log(yield select((state) => state.loading));
yield take(LOADING_DONE);
console.log('A3');
}
const err = yield select(errorSelector);
if (err.length) {
console.log('A4');
throw err[0];
}
console.log('A5');
}
export function* renderUntilDone() {
let again = true;
let renderCount = 0;
let html;
yield take(LOADING_DONE);
yield onChange(true);
function* onChange(first = false) {
const isLoading = yield select(isLoadingSelector);
if (!first && isLoading) {
again = true;
return;
}
if (again) {
again = false;
render();
}
// This may not work if onChange isn't called sync during render
if (!again) {
unsub();
return html;
}
}
function render() {
renderCount++;
if (renderCount > MAX_RENDERS) {
unsub();
return reject(new Error(`Tried to render more than ${MAX_RENDERS} times.`));
}
const state = store.getState();
html = ReactDOMServer.renderToString(app(state));
}
}
// TODO(@geophree): Add an makeLoadingActionCreator, and takeEveryLoading
// TODO(@geophree): what we really want is a way to wait until all actions have resolved through the sagas,
// and all finite sagas (sagas we expect to complete vs infinite-loop sagas) have completed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment