mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Refactor saga code in ducks files and add sdk as parameter.
This commit is contained in:
parent
2a9c9e103b
commit
b937f3fd57
7 changed files with 127 additions and 38 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import/prefer-default-export */
|
||||
export const fakeListings = [
|
||||
/* eslint-disable import/prefer-default-export, no-unused-vars */
|
||||
const fakeListings = [
|
||||
{
|
||||
id: 123,
|
||||
title: 'Banyan Studios',
|
||||
|
|
@ -24,3 +24,19 @@ export const fakeListings = [
|
|||
},
|
||||
];
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
|
||||
exports.fetchListings = () => {
|
||||
const randomTime = Math.random() * 2000;
|
||||
const fakeResponseTime = 1000 + randomTime;
|
||||
|
||||
const fakeFetch = new Promise((resolve, reject) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
resolve({ response: fakeListings });
|
||||
// or reject({ error: new Error('FetchListings errored') );
|
||||
},
|
||||
fakeResponseTime,
|
||||
);
|
||||
});
|
||||
return fakeFetch;
|
||||
};
|
||||
|
|
@ -26,8 +26,9 @@ const url = require('url');
|
|||
const _ = require('lodash');
|
||||
const React = require('react');
|
||||
const { createServerRenderContext } = require('react-router');
|
||||
const auth = require('./auth');
|
||||
const sagaEffects = require('redux-saga/effects');
|
||||
const auth = require('./auth');
|
||||
const sdk = require('./fakeSDK');
|
||||
|
||||
// Construct the bundle path where the server side rendering function
|
||||
// can be imported.
|
||||
|
|
@ -74,16 +75,21 @@ const template = _.template(indexHtml, {
|
|||
function fetchInitialState(requestUrl) {
|
||||
const pathname = url.parse(requestUrl).pathname;
|
||||
const { matchedRoutes, params } = matchPathname(pathname);
|
||||
|
||||
// pathname may match with several routes (if they don't have exactly=true)
|
||||
// We filter all the components form matched routes that have `loadData`
|
||||
const initialFetches = _
|
||||
.chain(matchedRoutes)
|
||||
.filter(r => r.loadData && r.loadData.fetchData)
|
||||
.map(r => sagaEffects.fork(r.loadData.fetchData))
|
||||
.map(r => sagaEffects.fork(r.loadData.fetchData, sdk))
|
||||
.value();
|
||||
|
||||
// We need to combine different onload sagas under one yield
|
||||
const fetchInitialData = function* fetchInitialData() {
|
||||
yield initialFetches;
|
||||
};
|
||||
|
||||
// runSaga (if necessary) and return initial store state after loadData fetches.
|
||||
if (initialFetches.length > 0) {
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
const store = configureStore({});
|
||||
|
|
@ -94,6 +100,7 @@ function fetchInitialState(requestUrl) {
|
|||
.catch(e => {
|
||||
reject(e);
|
||||
});
|
||||
// Close temporary store (which was used only for fetching initial store state)
|
||||
store.close();
|
||||
});
|
||||
return fetchPromise;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@
|
|||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
import { unionWith, isEqual } from 'lodash';
|
||||
import { delay } from 'redux-saga';
|
||||
import { call, take, put } from 'redux-saga/effects';
|
||||
import { fakeListings } from './fakeData';
|
||||
import { createRequestTypes } from '../../util/sagaHelpers';
|
||||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const ADD_FILTER = 'app/SearchPage/ADD_FILTER';
|
||||
export const LOAD_LISTINGS = 'app/SearchPage/LOAD_LISTINGS';
|
||||
export const LOAD_LISTINGS_SUCCESS = 'app/SearchPage/LOAD_LISTINGS_SUCCESS';
|
||||
export const LOAD_LISTINGS_FAILURE = 'app/SearchPage/LOAD_LISTINGS_FAILURE';
|
||||
|
||||
// async actions use request - success / failure pattern
|
||||
export const LOAD_LISTINGS = createRequestTypes('app/SearchPage/LOAD_LISTINGS');
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
|
|
@ -26,17 +25,16 @@ export const initialState = {
|
|||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case ADD_FILTER: {
|
||||
const stateFilters = state.filters || [];
|
||||
return { ...state, ...{ filters: unionWith(stateFilters, [payload], isEqual) } };
|
||||
}
|
||||
case LOAD_LISTINGS:
|
||||
return { ...state, loadingListings: true, initialListingsLoaded: false };
|
||||
case LOAD_LISTINGS_SUCCESS:
|
||||
return { ...state, loadingListings: false, initialListingsLoaded: true, listings: payload };
|
||||
case LOAD_LISTINGS_FAILURE:
|
||||
return { ...state, loadingListings: false, loadListingError: payload };
|
||||
case LOAD_LISTINGS.REQUEST:
|
||||
case LOAD_LISTINGS.SUCCESS:
|
||||
case LOAD_LISTINGS.FAILURE:
|
||||
return { ...state, ...payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
@ -45,37 +43,42 @@ export default function reducer(state = initialState, action = {}) {
|
|||
// ================ Action creators ================ //
|
||||
|
||||
export const addFilter = (key, value) => ({ type: ADD_FILTER, payload: { [key]: value } });
|
||||
export const loadListings = () => ({ type: LOAD_LISTINGS, payload: {} });
|
||||
export const loadListingsSuccess = listings => ({ type: LOAD_LISTINGS_SUCCESS, payload: listings });
|
||||
export const loadListingsFailure = error => ({
|
||||
type: LOAD_LISTINGS_FAILURE,
|
||||
payload: error,
|
||||
error: true,
|
||||
});
|
||||
|
||||
// async actions use request - success - failure pattern
|
||||
const action = (type, payload = {}) => ({ type, ...payload });
|
||||
|
||||
export const loadListings = {
|
||||
request: () =>
|
||||
action(LOAD_LISTINGS.REQUEST, {
|
||||
payload: { loadingListings: true, initialListingsLoaded: false },
|
||||
}),
|
||||
success: listings =>
|
||||
action(LOAD_LISTINGS.SUCCESS, {
|
||||
payload: { loadingListings: false, initialListingsLoaded: true, listings },
|
||||
}),
|
||||
failure: error =>
|
||||
action(LOAD_LISTINGS.FAILURE, {
|
||||
payload: { loadingListings: false, loadListingError: error, error: true },
|
||||
}),
|
||||
};
|
||||
|
||||
// ================ Worker sagas ================ //
|
||||
|
||||
export function fetchListings() {
|
||||
const randomTime = Math.random() * 2000;
|
||||
const fakeResponseTime = 1000 + randomTime;
|
||||
return delay(fakeResponseTime, { response: fakeListings });
|
||||
}
|
||||
|
||||
export function* callFetchListings() {
|
||||
const { response, error } = yield call(fetchListings);
|
||||
export function* callFetchListings(sdk) {
|
||||
const { response, error } = yield call(sdk.fetchListings);
|
||||
if (response) {
|
||||
yield put(loadListingsSuccess(response));
|
||||
yield put(loadListings.success(response));
|
||||
} else {
|
||||
yield put(loadListingsFailure(error));
|
||||
yield put(loadListings.failure(error));
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Watcher sagas ================ //
|
||||
|
||||
export function* watchLoadListings() {
|
||||
export function* watchLoadListings(sdk) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
yield take(LOAD_LISTINGS);
|
||||
yield call(callFetchListings);
|
||||
yield take(LOAD_LISTINGS.REQUEST);
|
||||
yield call(callFetchListings, sdk);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ import {
|
|||
export class SearchPageComponent extends Component {
|
||||
componentDidMount() {
|
||||
// TODO: This should be moved to Router
|
||||
this.props.onLoadListings();
|
||||
const { SearchPage } = this.props;
|
||||
if (!SearchPage.initialListingsLoaded) {
|
||||
this.props.onLoadListings();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -90,7 +93,7 @@ const mapDispatchToProps = function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
onAddNotice: msg => dispatch(addFlashNotification('notice', msg)),
|
||||
onAddFilter: (k, v) => dispatch(addFilter(k, v)),
|
||||
onLoadListings: () => dispatch(loadListings()),
|
||||
onLoadListings: () => dispatch(loadListings.request()),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { watchLogin, watchLogout } from './ducks/Auth.ducks';
|
||||
import { watchLoadListings } from './containers/SearchPage/SearchPage.ducks';
|
||||
import sdk from './util/fakeSDK';
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield [watchLogin(), watchLogout(), watchLoadListings()];
|
||||
yield [watchLogin(), watchLogout(), watchLoadListings(sdk)];
|
||||
}
|
||||
|
|
|
|||
45
src/util/fakeSDK.js
Normal file
45
src/util/fakeSDK.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* eslint-disable import/prefer-default-export, no-unused-vars */
|
||||
export const fakeListings = [
|
||||
{
|
||||
id: 123,
|
||||
title: 'Banyan Studios',
|
||||
price: '55\u20AC / day',
|
||||
description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
|
||||
location: 'New York, NY \u2022 40mi away',
|
||||
review: { count: '8 reviews', rating: '4' },
|
||||
author: {
|
||||
name: 'The Stardust Collective',
|
||||
avatar: 'http://placehold.it/44x44',
|
||||
review: { rating: '4' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 1234,
|
||||
title: 'Pienix Studio',
|
||||
price: '80\u20AC / day',
|
||||
description: 'Pienix Studio specializes in music mixing and mastering production.',
|
||||
location: 'New York, NY \u2022 6mi away',
|
||||
review: { count: '7 reviews', rating: '4' },
|
||||
author: { name: 'Juhan', avatar: 'http://placehold.it/44x44', review: { rating: '4' } },
|
||||
},
|
||||
];
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
|
||||
const fakeSDK = {
|
||||
fetchListings: () => {
|
||||
const randomTime = Math.random() * 2000;
|
||||
const fakeResponseTime = 1000 + randomTime;
|
||||
|
||||
const fakeFetch = new Promise((resolve, reject) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
resolve({ response: fakeListings });
|
||||
// or reject({ error: new Error('FetchListings errored') );
|
||||
},
|
||||
fakeResponseTime,
|
||||
);
|
||||
});
|
||||
return fakeFetch;
|
||||
},
|
||||
};
|
||||
export default fakeSDK;
|
||||
14
src/util/sagaHelpers.js
Normal file
14
src/util/sagaHelpers.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
// async actions use request - success / failure pattern
|
||||
const REQUEST = 'REQUEST';
|
||||
const SUCCESS = 'SUCCESS';
|
||||
const FAILURE = 'FAILURE';
|
||||
|
||||
// response format: { REQUEST: 'baseStr.REQUEST', SUCCESS: 'baseStr.SUCCESS', FAILURE: 'baseStr.FAILURE' }
|
||||
export const createRequestTypes = base => [REQUEST, SUCCESS, FAILURE].reduce((acc, type) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
acc[type] = `${base}.${type}`;
|
||||
return acc;
|
||||
}, {});
|
||||
/* eslint-enable import/prefer-default-export */
|
||||
Loading…
Add table
Reference in a new issue