I'm having issues with waiting "one action flow" to finish from start until the end. The app works as usual in client, but while doing stuff server-side it doesn't finish until the end.
To clarify, I'm using both "next-redux-wrapper" and "next-redux-saga" properly, as I can achieve the delayed rendering when I use simpler flow with sagas.
I think the mistake is mostly how I understand (or not) saga effects altogether.
Some of the code is omitted for brevity.
api.saga.js
const makeRequest = ({ body, url, method }) =>
axios({ method, url: url, data: body });
export function* requestWatcher(action) {
const { feature } = action.meta;
try {
const { data: response } = yield call(makeRequest, action.meta);
yield put(apiSuccess({ response, feature }));
} catch (error) {
yield put(apiError({ error: error, feature }));
}
}
export default function* apiSaga() {
yield takeEvery(action => action.type.includes(API_REQUEST), requestWatcher);
}
smallBusiness.saga.js
function* watchApiSuccess(action) {
yield put(
setPositions({ positions: action.payload.positions })
);
yield put(setLoader({ isLoading: false, feature: SMALL_BUSINESS }));
}
function* watchApiError() {
yield put(setLoader({ isLoading: false, feature: SMALL_BUSINESS }));
}
function* watchFetchPositions() {
yield put(
apiRequest({
body: null,
method: 'GET',
url: SMALL_BUSINESS_URL,
feature: SMALL_BUSINESS
})
);
yield put(setLoader({ isLoading: true, feature: SMALL_BUSINESS }));
}
export default function* smallBusinessSaga() {
yield all([
takeLatest(`${SMALL_BUSINESS} ${API_SUCCESS}`, watchApiSuccess),
takeLatest(`${SMALL_BUSINESS} ${API_ERROR}`, watchApiError),
takeLatest(FETCH_POSITIONS, watchFetchPositions)
]);
}
rootSaga.js
export default function* rootSaga() {
yield all([call(smallBusinessSaga), call(apiSaga)]);
}
configureStore.js
store.sagaTask = sagaMiddleware.run(rootSaga);
Client-side function "watchApiSuccess" is called, but server-side it's not. Every generator function is called server-side except for the one already mentioned.
When I simplify the flow with something like the code example below, app will pre-render server-side.
function* watchPrefetchPositions() {
const meta = {
body: null,
method: 'GET',
url: SMALL_BUSINESS_URL,
feature: SMALL_BUSINESS
};
const { data: response } = yield call(makeRequest, meta);
yield put(setPositions({ positions: response.positions }));
}
export default function* smallBusinessSaga() {
yield all([
...
takeLatest(PRE_FETCH_POSITIONS, watchPrefetchPositions)
]);
}
The main issue I have with being unable to have complex flows is that sagas won't be able to do things like normalize and more.
You can use flags in order to control when and if your components should render. This is a common solution for rendering a fallback UI (e.g: a spinner or a text) in order to wait until an async process (saga, thunk, API service etc) is finished and the component has all it needs to render itself.
I have attached a CodeSandBox example here so you can see how it works in a glimpse. https://codesandbox.io/embed/2586j3k1p
You can see that loading appears for a moment until the data is fetched. Note that CodeSandBox is caching this API call so you barely notice the loading text after the first click).
React road-map includes a release for a feature dealing with Async Rendering called "Suspense", which imitates the same behavior for this specific type of flow issues as you described. Until React Async Rendering will be released I recommend using flags.
If you would like more details, I recommend watching Dan Abramov: Beyond React 16 | JSConf Iceland 2018
Good Luck