From cec4ea15c773eb06784cc67335fb0e2690229004 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 21 Feb 2017 20:59:18 +0200 Subject: [PATCH] Add duck file for API calls --- src/ducks/index.js | 3 +- src/ducks/sdk.duck.js | 172 ++++++++++++++++++++++++++++++++++++++++++ src/sagas.js | 3 +- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/ducks/sdk.duck.js diff --git a/src/ducks/index.js b/src/ducks/index.js index 4d71de3d..89abad1e 100644 --- a/src/ducks/index.js +++ b/src/ducks/index.js @@ -8,5 +8,6 @@ import { reducer as form } from 'redux-form'; import Auth from './Auth.duck'; import FlashNotification from './FlashNotification.duck'; import LocationFilter from './LocationFilter.duck'; +import sdkReducer from './sdk.duck'; -export { form, Auth, FlashNotification, LocationFilter }; +export { form, Auth, FlashNotification, LocationFilter, sdkReducer as data }; diff --git a/src/ducks/sdk.duck.js b/src/ducks/sdk.duck.js new file mode 100644 index 00000000..c81b1a06 --- /dev/null +++ b/src/ducks/sdk.duck.js @@ -0,0 +1,172 @@ +/* eslint-disable no-constant-condition, no-console */ +import { call, put, take, fork } from 'redux-saga/effects'; +import { mergeEntities } from '../util/data'; + +const requestAction = actionType => params => ({ type: actionType, payload: { params } }); + +const successAction = actionType => data => ({ type: actionType, payload: data }); + +const errorAction = actionType => error => ({ type: actionType, payload: error, error: true }); + +// ================ Action types ================ // + +export const SHOW_LISTINGS_REQUEST = 'app/sdk/SHOW_LISTINGS_REQUEST'; +export const SHOW_LISTINGS_SUCCESS = 'app/sdk/SHOW_LISTINGS_SUCCESS'; +export const SHOW_LISTINGS_ERROR = 'app/sdk/SHOW_LISTINGS_ERROR'; + +export const QUERY_LISTINGS_REQUEST = 'app/sdk/QUERY_LISTINGS_REQUEST'; +export const QUERY_LISTINGS_SUCCESS = 'app/sdk/QUERY_LISTINGS_SUCCESS'; +export const QUERY_LISTINGS_ERROR = 'app/sdk/QUERY_LISTINGS_ERROR'; + +export const SEARCH_LISTINGS_REQUEST = 'app/sdk/SEARCH_LISTINGS_REQUEST'; +export const SEARCH_LISTINGS_SUCCESS = 'app/sdk/SEARCH_LISTINGS_SUCCESS'; +export const SEARCH_LISTINGS_ERROR = 'app/sdk/SEARCH_LISTINGS_ERROR'; + +export const SHOW_MARKETPLACE_REQUEST = 'app/sdk/SHOW_MARKETPLACE_REQUEST'; +export const SHOW_MARKETPLACE_SUCCESS = 'app/sdk/SHOW_MARKETPLACE_SUCCESS'; +export const SHOW_MARKETPLACE_ERROR = 'app/sdk/SHOW_MARKETPLACE_ERROR'; + +export const SHOW_USERS_REQUEST = 'app/sdk/SHOW_USERS_REQUEST'; +export const SHOW_USERS_SUCCESS = 'app/sdk/SHOW_USERS_SUCCESS'; +export const SHOW_USERS_ERROR = 'app/sdk/SHOW_USERS_ERROR'; + +// ================ Reducer ================ // + +const initialState = { + // Error instance placeholders for each endpoint + showListingsError: null, + queryListingsError: null, + searchListingsError: null, + showMarketplaceError: null, + showUsersError: null, + // Database of all the fetched entities. + entities: {}, +}; + +const merge = (state, payload) => { + const entities = mergeEntities(state.entities, payload.data); + return { ...state, entities }; +}; + +export default function sdkReducer(state = initialState, action = {}) { + const { type, payload } = action; + switch (type) { + case SHOW_LISTINGS_REQUEST: + return { ...state, showListingsError: null }; + case SHOW_LISTINGS_SUCCESS: + return merge(state, payload); + case SHOW_LISTINGS_ERROR: + console.error(payload); + return { ...state, showListingsError: payload }; + + case QUERY_LISTINGS_REQUEST: + return { ...state, queryListingsError: null }; + case QUERY_LISTINGS_SUCCESS: + return merge(state, payload); + case QUERY_LISTINGS_ERROR: + console.error(payload); + return { ...state, queryListingsError: payload }; + + case SEARCH_LISTINGS_REQUEST: + return { ...state, searchListingsError: null }; + case SEARCH_LISTINGS_SUCCESS: + return merge(state, payload); + case SEARCH_LISTINGS_ERROR: + console.error(payload); + return { ...state, searchListingsError: payload }; + + case SHOW_MARKETPLACE_REQUEST: + return { ...state, showMarketplaceError: null }; + case SHOW_MARKETPLACE_SUCCESS: + return merge(state, payload); + case SHOW_MARKETPLACE_ERROR: + console.error(payload); + return { ...state, showMarketplaceError: payload }; + + case SHOW_USERS_REQUEST: + return { ...state, showUsersError: null }; + case SHOW_USERS_SUCCESS: + return merge(state, payload); + case SHOW_USERS_ERROR: + console.error(payload); + return { ...state, showUsersError: payload }; + + default: + return state; + } +} + +// ================ Action creators ================ // + +export const showListings = requestAction(SHOW_LISTINGS_REQUEST); +export const showListingsSuccess = successAction(SHOW_LISTINGS_SUCCESS); +export const showListingsError = errorAction(SHOW_LISTINGS_ERROR); + +export const queryListings = requestAction(QUERY_LISTINGS_REQUEST); +export const queryListingsSuccess = successAction(QUERY_LISTINGS_SUCCESS); +export const queryListingsError = errorAction(QUERY_LISTINGS_ERROR); + +export const searchListings = requestAction(SEARCH_LISTINGS_REQUEST); +export const searchListingsSuccess = successAction(SEARCH_LISTINGS_SUCCESS); +export const searchListingsError = errorAction(SEARCH_LISTINGS_ERROR); + +export const showMarketplace = requestAction(SHOW_MARKETPLACE_REQUEST); +export const showMarketplaceSuccess = successAction(SHOW_MARKETPLACE_SUCCESS); +export const showMarketplaceError = errorAction(SHOW_MARKETPLACE_ERROR); + +export const showUsers = requestAction(SHOW_USERS_REQUEST); +export const showUsersSuccess = successAction(SHOW_USERS_SUCCESS); +export const showUsersError = errorAction(SHOW_USERS_ERROR); + +// ================ Worker sagas ================ // + +function* callEndpoint(sdkMethod, success, error, action) { + const params = action.payload.params; + try { + const response = yield call(sdkMethod, params); + yield put(success(response)); + } catch (e) { + yield put(error(e)); + } +} + +// ================ Watcher sagas ================ // + +// TODO: Think about structuring this file to avoid repetition without +// too much metaprogramming that makes the code harder to understand. +const endpoints = { + [SHOW_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.show, + success: showListingsSuccess, + error: showListingsError, + }, + [QUERY_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.query, + success: queryListingsSuccess, + error: queryListingsError, + }, + [SEARCH_LISTINGS_REQUEST]: { + method: sdk => sdk.listings.search, + success: searchListingsSuccess, + error: searchListingsError, + }, + [SHOW_MARKETPLACE_REQUEST]: { + method: sdk => sdk.marketplace.show, + success: showMarketplaceSuccess, + error: showMarketplaceError, + }, + [SHOW_USERS_REQUEST]: { + method: sdk => sdk.users.show, + success: showUsersSuccess, + error: showUsersError, + }, +}; + +export function* watchSdk(sdk) { + const requestTypes = Object.keys(endpoints); + while (true) { + const action = yield take(requestTypes); + const { method, success, error } = endpoints[action.type]; + yield fork(callEndpoint, method(sdk), success, error, action); + } +} diff --git a/src/sagas.js b/src/sagas.js index 41af2d2b..7f95eb15 100644 --- a/src/sagas.js +++ b/src/sagas.js @@ -1,8 +1,9 @@ import { watchAuth } from './ducks/Auth.duck'; import { watchLoadListings } from './containers/SearchPage/SearchPage.duck'; +import { watchSdk } from './ducks/sdk.duck'; const createRootSaga = sdk => function* rootSaga() { - yield [watchAuth(sdk), watchLoadListings(sdk)]; + yield [watchAuth(sdk), watchLoadListings(sdk), watchSdk(sdk)]; }; export default createRootSaga;