diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7161c6..f3a19b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] + +#### Added + +* *Nothing* + +#### Changed + +* [#125](https://github.com/shlinkio/shlink-web-client/issues/125) Refactored reducers to replace `switch` statements by `handleActions` from [redux-actions](https://github.com/redux-utilities/redux-actions). + +#### Deprecated + +* *Nothing* + +#### Removed + +* *Nothing* + +#### Fixed + +* *Nothing* + + ## 2.0.3 - 2019-03-16 #### Added diff --git a/package.json b/package.json index 457521b4..29cb437b 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react-tagsinput": "^3.19.0", "reactstrap": "^6.0.1", "redux": "^4.0.0", + "redux-actions": "^2.6.5", "redux-thunk": "^2.3.0", "uuid": "^3.3.2" }, diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index 51b53e86..2d35987f 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -1,24 +1,14 @@ +import { createAction, handleActions } from 'redux-actions'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; export const RESET_SELECTED_SERVER = 'shlink/selectedServer/RESET_SELECTED_SERVER'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ -const defaultState = null; +const initialState = null; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case SELECT_SERVER: - return action.selectedServer; - case RESET_SELECTED_SERVER: - return defaultState; - default: - return state; - } -} - -export const resetSelectedServer = () => ({ type: RESET_SELECTED_SERVER }); +export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); export const selectServer = (serversService) => (serverId) => (dispatch) => { dispatch(resetShortUrlParams()); @@ -30,3 +20,8 @@ export const selectServer = (serversService) => (serverId) => (dispatch) => { selectedServer, }); }; + +export default handleActions({ + [RESET_SELECTED_SERVER]: () => initialState, + [SELECT_SERVER]: (state, { selectedServer }) => selectedServer, +}, initialState); diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js index 44837d15..1a3955f6 100644 --- a/src/servers/reducers/server.js +++ b/src/servers/reducers/server.js @@ -1,33 +1,16 @@ +import { createAction, handleActions } from 'redux-actions'; +import { pipe } from 'ramda'; + export const FETCH_SERVERS = 'shlink/servers/FETCH_SERVERS'; -export default function reducer(state = {}, action) { - switch (action.type) { - case FETCH_SERVERS: - return action.servers; - default: - return state; - } -} +export const listServers = ({ listServers }) => createAction(FETCH_SERVERS, () => listServers()); -export const listServers = (serversService) => () => ({ - type: FETCH_SERVERS, - servers: serversService.listServers(), -}); +export const createServer = ({ createServer }, listServers) => pipe(createServer, listServers); -export const createServer = (serversService, listServers) => (server) => { - serversService.createServer(server); +export const deleteServer = ({ deleteServer }, listServers) => pipe(deleteServer, listServers); - return listServers(); -}; +export const createServers = ({ createServers }, listServers) => pipe(createServers, listServers); -export const deleteServer = (serversService, listServers) => (server) => { - serversService.deleteServer(server); - - return listServers(); -}; - -export const createServers = (serversService, listServers) => (servers) => { - serversService.createServers(servers); - - return listServers(); -}; +export default handleActions({ + [FETCH_SERVERS]: (state, { payload }) => payload, +}, {}); diff --git a/src/short-urls/reducers/shortUrlCreation.js b/src/short-urls/reducers/shortUrlCreation.js index 8a6d772f..2cd4e9e3 100644 --- a/src/short-urls/reducers/shortUrlCreation.js +++ b/src/short-urls/reducers/shortUrlCreation.js @@ -1,11 +1,12 @@ import PropTypes from 'prop-types'; +import { createAction, handleActions } from 'redux-actions'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; export const CREATE_SHORT_URL_ERROR = 'shlink/createShortUrl/CREATE_SHORT_URL_ERROR'; export const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL'; export const RESET_CREATE_SHORT_URL = 'shlink/createShortUrl/RESET_CREATE_SHORT_URL'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const createShortUrlResultType = PropTypes.shape({ result: PropTypes.shape({ @@ -15,38 +16,18 @@ export const createShortUrlResultType = PropTypes.shape({ error: PropTypes.bool, }); -const defaultState = { +const initialState = { result: null, saving: false, error: false, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case CREATE_SHORT_URL_START: - return { - ...state, - saving: true, - error: false, - }; - case CREATE_SHORT_URL_ERROR: - return { - ...state, - saving: false, - error: true, - }; - case CREATE_SHORT_URL: - return { - result: action.result, - saving: false, - error: false, - }; - case RESET_CREATE_SHORT_URL: - return defaultState; - default: - return state; - } -} +export default handleActions({ + [CREATE_SHORT_URL_START]: (state) => ({ ...state, saving: true, error: false }), + [CREATE_SHORT_URL_ERROR]: (state) => ({ ...state, saving: false, error: true }), + [CREATE_SHORT_URL]: (state, { result }) => ({ result, saving: false, error: false }), + [RESET_CREATE_SHORT_URL]: () => initialState, +}, initialState); export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => { dispatch({ type: CREATE_SHORT_URL_START }); @@ -63,4 +44,4 @@ export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatc } }; -export const resetCreateShortUrl = () => ({ type: RESET_CREATE_SHORT_URL }); +export const resetCreateShortUrl = createAction(RESET_CREATE_SHORT_URL); diff --git a/src/short-urls/reducers/shortUrlDeletion.js b/src/short-urls/reducers/shortUrlDeletion.js index 6d2f2f52..2c2a568b 100644 --- a/src/short-urls/reducers/shortUrlDeletion.js +++ b/src/short-urls/reducers/shortUrlDeletion.js @@ -1,12 +1,13 @@ +import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; export const DELETE_SHORT_URL_ERROR = 'shlink/deleteShortUrl/DELETE_SHORT_URL_ERROR'; export const DELETE_SHORT_URL = 'shlink/deleteShortUrl/DELETE_SHORT_URL'; export const RESET_DELETE_SHORT_URL = 'shlink/deleteShortUrl/RESET_DELETE_SHORT_URL'; export const SHORT_URL_DELETED = 'shlink/deleteShortUrl/SHORT_URL_DELETED'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const shortUrlDeletionType = PropTypes.shape({ shortCode: PropTypes.string.isRequired, @@ -18,41 +19,19 @@ export const shortUrlDeletionType = PropTypes.shape({ }).isRequired, }); -const defaultState = { +const initialState = { shortCode: '', loading: false, error: false, errorData: {}, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case DELETE_SHORT_URL_START: - return { - ...state, - loading: true, - error: false, - }; - case DELETE_SHORT_URL_ERROR: - return { - ...state, - loading: false, - error: true, - errorData: action.errorData, - }; - case DELETE_SHORT_URL: - return { - ...state, - shortCode: action.shortCode, - loading: false, - error: false, - }; - case RESET_DELETE_SHORT_URL: - return defaultState; - default: - return state; - } -} +export default handleActions({ + [DELETE_SHORT_URL_START]: (state) => ({ ...state, loading: true, error: false }), + [DELETE_SHORT_URL_ERROR]: (state, { errorData }) => ({ ...state, errorData, loading: false, error: true }), + [DELETE_SHORT_URL]: (state, { shortCode }) => ({ ...state, shortCode, loading: false, error: false }), + [RESET_DELETE_SHORT_URL]: () => initialState, +}, initialState); export const deleteShortUrl = (buildShlinkApiClient) => (shortCode) => async (dispatch, getState) => { dispatch({ type: DELETE_SHORT_URL_START }); @@ -70,6 +49,6 @@ export const deleteShortUrl = (buildShlinkApiClient) => (shortCode) => async (di } }; -export const resetDeleteShortUrl = () => ({ type: RESET_DELETE_SHORT_URL }); +export const resetDeleteShortUrl = createAction(RESET_DELETE_SHORT_URL); export const shortUrlDeleted = (shortCode) => ({ type: SHORT_URL_DELETED, shortCode }); diff --git a/src/short-urls/reducers/shortUrlTags.js b/src/short-urls/reducers/shortUrlTags.js index a0390a60..9c9f8149 100644 --- a/src/short-urls/reducers/shortUrlTags.js +++ b/src/short-urls/reducers/shortUrlTags.js @@ -1,13 +1,14 @@ +import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; import { pick } from 'ramda'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; export const EDIT_SHORT_URL_TAGS_ERROR = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_ERROR'; export const EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS'; export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS'; export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const shortUrlTagsType = PropTypes.shape({ shortCode: PropTypes.string, @@ -16,39 +17,19 @@ export const shortUrlTagsType = PropTypes.shape({ error: PropTypes.bool.isRequired, }); -const defaultState = { +const initialState = { shortCode: null, tags: [], saving: false, error: false, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case EDIT_SHORT_URL_TAGS_START: - return { - ...state, - saving: true, - error: false, - }; - case EDIT_SHORT_URL_TAGS_ERROR: - return { - ...state, - saving: false, - error: true, - }; - case EDIT_SHORT_URL_TAGS: - return { - ...pick([ 'shortCode', 'tags' ], action), - saving: false, - error: false, - }; - case RESET_EDIT_SHORT_URL_TAGS: - return defaultState; - default: - return state; - } -} +export default handleActions({ + [EDIT_SHORT_URL_TAGS_START]: (state) => ({ ...state, saving: true, error: false }), + [EDIT_SHORT_URL_TAGS_ERROR]: (state) => ({ ...state, saving: false, error: true }), + [EDIT_SHORT_URL_TAGS]: (state, action) => ({ ...pick([ 'shortCode', 'tags' ], action), saving: false, error: false }), + [RESET_EDIT_SHORT_URL_TAGS]: () => initialState, +}, initialState); export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, tags) => async (dispatch, getState) => { dispatch({ type: EDIT_SHORT_URL_TAGS_START }); @@ -66,7 +47,7 @@ export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, tags) => a } }; -export const resetShortUrlsTags = () => ({ type: RESET_EDIT_SHORT_URL_TAGS }); +export const resetShortUrlsTags = createAction(RESET_EDIT_SHORT_URL_TAGS); export const shortUrlTagsEdited = (shortCode, tags) => ({ tags, diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index fa4d4fe5..e48c0492 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -1,13 +1,14 @@ +import { handleActions } from 'redux-actions'; import { assoc, assocPath, propEq, reject } from 'ramda'; import PropTypes from 'prop-types'; import { SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const shortUrlType = PropTypes.shape({ shortCode: PropTypes.string, @@ -22,39 +23,24 @@ const initialState = { error: false, }; -export default function reducer(state = initialState, action) { - switch (action.type) { - case LIST_SHORT_URLS_START: - return { ...state, loading: true, error: false }; - case LIST_SHORT_URLS: - return { - loading: false, - error: false, - shortUrls: action.shortUrls, - }; - case LIST_SHORT_URLS_ERROR: - return { - loading: false, - error: true, - shortUrls: {}, - }; - case SHORT_URL_TAGS_EDITED: - const { data } = state.shortUrls; +export default handleActions({ + [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), + [LIST_SHORT_URLS]: (state, { shortUrls }) => ({ loading: false, error: false, shortUrls }), + [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: {} }), + [SHORT_URL_TAGS_EDITED]: (state, action) => { // eslint-disable-line object-shorthand + const { data } = state.shortUrls; - return assocPath([ 'shortUrls', 'data' ], data.map((shortUrl) => - shortUrl.shortCode === action.shortCode - ? assoc('tags', action.tags, shortUrl) - : shortUrl), state); - case SHORT_URL_DELETED: - return assocPath( - [ 'shortUrls', 'data' ], - reject(propEq('shortCode', action.shortCode), state.shortUrls.data), - state, - ); - default: - return state; - } -} + return assocPath([ 'shortUrls', 'data' ], data.map((shortUrl) => + shortUrl.shortCode === action.shortCode + ? assoc('tags', action.tags, shortUrl) + : shortUrl), state); + }, + [SHORT_URL_DELETED]: (state, action) => assocPath( + [ 'shortUrls', 'data' ], + reject(propEq('shortCode', action.shortCode), state.shortUrls.data), + state, + ), +}, initialState); export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => { dispatch({ type: LIST_SHORT_URLS_START }); diff --git a/src/short-urls/reducers/shortUrlsListParams.js b/src/short-urls/reducers/shortUrlsListParams.js index ef8de0b6..14962362 100644 --- a/src/short-urls/reducers/shortUrlsListParams.js +++ b/src/short-urls/reducers/shortUrlsListParams.js @@ -1,3 +1,4 @@ +import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; import { LIST_SHORT_URLS } from './shortUrlsList'; @@ -9,17 +10,11 @@ export const shortUrlsListParamsType = PropTypes.shape({ searchTerm: PropTypes.string, }); -const defaultState = { page: '1' }; +const initialState = { page: '1' }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case LIST_SHORT_URLS: - return { ...state, ...action.params }; - case RESET_SHORT_URL_PARAMS: - return defaultState; - default: - return state; - } -} +export default handleActions({ + [LIST_SHORT_URLS]: (state, { params }) => ({ ...state, ...params }), + [RESET_SHORT_URL_PARAMS]: () => initialState, +}, initialState); -export const resetShortUrlParams = () => ({ type: RESET_SHORT_URL_PARAMS }); +export const resetShortUrlParams = createAction(RESET_SHORT_URL_PARAMS); diff --git a/src/tags/reducers/tagDelete.js b/src/tags/reducers/tagDelete.js index 03c42947..d8fd747a 100644 --- a/src/tags/reducers/tagDelete.js +++ b/src/tags/reducers/tagDelete.js @@ -1,43 +1,28 @@ +import { handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; export const DELETE_TAG_ERROR = 'shlink/deleteTag/DELETE_TAG_ERROR'; export const DELETE_TAG = 'shlink/deleteTag/DELETE_TAG'; export const TAG_DELETED = 'shlink/deleteTag/TAG_DELETED'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const tagDeleteType = PropTypes.shape({ deleting: PropTypes.bool, error: PropTypes.bool, }); -const defaultState = { +const initialState = { deleting: false, error: false, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case DELETE_TAG_START: - return { - deleting: true, - error: false, - }; - case DELETE_TAG_ERROR: - return { - deleting: false, - error: true, - }; - case DELETE_TAG: - return { - deleting: false, - error: false, - }; - default: - return state; - } -} +export default handleActions({ + [DELETE_TAG_START]: () => ({ deleting: true, error: false }), + [DELETE_TAG_ERROR]: () => ({ deleting: false, error: true }), + [DELETE_TAG]: () => ({ deleting: false, error: false }), +}, initialState); export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => { dispatch({ type: DELETE_TAG_START }); diff --git a/src/tags/reducers/tagEdit.js b/src/tags/reducers/tagEdit.js index 950e95db..2613169e 100644 --- a/src/tags/reducers/tagEdit.js +++ b/src/tags/reducers/tagEdit.js @@ -1,44 +1,30 @@ import { pick } from 'ramda'; +import { handleActions } from 'redux-actions'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; export const EDIT_TAG_ERROR = 'shlink/editTag/EDIT_TAG_ERROR'; export const EDIT_TAG = 'shlink/editTag/EDIT_TAG'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const TAG_EDITED = 'shlink/editTag/TAG_EDITED'; -const defaultState = { +const initialState = { oldName: '', newName: '', editing: false, error: false, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case EDIT_TAG_START: - return { - ...state, - editing: true, - error: false, - }; - case EDIT_TAG_ERROR: - return { - ...state, - editing: false, - error: true, - }; - case EDIT_TAG: - return { - ...pick([ 'oldName', 'newName' ], action), - editing: false, - error: false, - }; - default: - return state; - } -} +export default handleActions({ + [EDIT_TAG_START]: (state) => ({ ...state, editing: true, error: false }), + [EDIT_TAG_ERROR]: (state) => ({ ...state, editing: false, error: true }), + [EDIT_TAG]: (state, action) => ({ + ...pick([ 'oldName', 'newName' ], action), + editing: false, + error: false, + }), +}, initialState); export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newName, color) => async ( dispatch, diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index 9b4fe65e..899be3e6 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -1,70 +1,45 @@ +import { handleActions } from 'redux-actions'; import { isEmpty, reject } from 'ramda'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../utils/services/ShlinkApiClientBuilder'; import { TAG_DELETED } from './tagDelete'; import { TAG_EDITED } from './tagEdit'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ -const defaultState = { +const initialState = { tags: [], filteredTags: [], loading: false, error: false, }; -export default function reducer(state = defaultState, action) { - switch (action.type) { - case LIST_TAGS_START: - return { - ...state, - loading: true, - error: false, - }; - case LIST_TAGS_ERROR: - return { - ...state, - loading: false, - error: true, - }; - case LIST_TAGS: - return { - tags: action.tags, - filteredTags: action.tags, - loading: false, - error: false, - }; - case TAG_DELETED: - return { - ...state, +const renameTag = (oldName, newName) => (tag) => tag === oldName ? newName : tag; +const rejectTag = (tags, tagToReject) => reject((tag) => tag === tagToReject, tags); - // FIXME This should be optimized somehow... - tags: reject((tag) => tag === action.tag, state.tags), - filteredTags: reject((tag) => tag === action.tag, state.filteredTags), - }; - case TAG_EDITED: - const renameTag = (tag) => tag === action.oldName ? action.newName : tag; - - return { - ...state, - - // FIXME This should be optimized somehow... - tags: state.tags.map(renameTag).sort(), - filteredTags: state.filteredTags.map(renameTag).sort(), - }; - case FILTER_TAGS: - return { - ...state, - filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(action.searchTerm)), - }; - default: - return state; - } -} +export default handleActions({ + [LIST_TAGS_START]: (state) => ({ ...state, loading: true, error: false }), + [LIST_TAGS_ERROR]: (state) => ({ ...state, loading: false, error: true }), + [LIST_TAGS]: (state, { tags }) => ({ tags, filteredTags: tags, loading: false, error: false }), + [TAG_DELETED]: (state, { tag }) => ({ + ...state, + tags: rejectTag(state.tags, tag), + filteredTags: rejectTag(state.filteredTags, tag), + }), + [TAG_EDITED]: (state, { oldName, newName }) => ({ + ...state, + tags: state.tags.map(renameTag(oldName, newName)).sort(), + filteredTags: state.filteredTags.map(renameTag(oldName, newName)).sort(), + }), + [FILTER_TAGS]: (state, { searchTerm }) => ({ + ...state, + filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(searchTerm)), + }), +}, initialState); export const _listTags = (buildShlinkApiClient, force = false) => async (dispatch, getState) => { const { tagsList, selectedServer } = getState(); diff --git a/src/visits/reducers/shortUrlDetail.js b/src/visits/reducers/shortUrlDetail.js index 385c8071..cd06db65 100644 --- a/src/visits/reducers/shortUrlDetail.js +++ b/src/visits/reducers/shortUrlDetail.js @@ -1,11 +1,12 @@ +import { handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; import { shortUrlType } from '../../short-urls/reducers/shortUrlsList'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const GET_SHORT_URL_DETAIL_START = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_START'; export const GET_SHORT_URL_DETAIL_ERROR = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_ERROR'; export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const shortUrlDetailType = PropTypes.shape({ shortUrl: shortUrlType, @@ -19,29 +20,11 @@ const initialState = { error: false, }; -export default function reducer(state = initialState, action) { - switch (action.type) { - case GET_SHORT_URL_DETAIL_START: - return { - ...state, - loading: true, - }; - case GET_SHORT_URL_DETAIL_ERROR: - return { - ...state, - loading: false, - error: true, - }; - case GET_SHORT_URL_DETAIL: - return { - shortUrl: action.shortUrl, - loading: false, - error: false, - }; - default: - return state; - } -} +export default handleActions({ + [GET_SHORT_URL_DETAIL_START]: (state) => ({ ...state, loading: true }), + [GET_SHORT_URL_DETAIL_ERROR]: (state) => ({ ...state, loading: false, error: true }), + [GET_SHORT_URL_DETAIL]: (state, { shortUrl }) => ({ shortUrl, loading: false, error: false }), +}, initialState); export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode) => async (dispatch, getState) => { dispatch({ type: GET_SHORT_URL_DETAIL_START }); diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.js index 5aedef99..16c1f14f 100644 --- a/src/visits/reducers/shortUrlVisits.js +++ b/src/visits/reducers/shortUrlVisits.js @@ -1,13 +1,14 @@ +import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; import { flatten, prop, range, splitEvery } from 'ramda'; -/* eslint-disable padding-line-between-statements, newline-after-var */ +/* eslint-disable padding-line-between-statements */ export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START'; export const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR'; export const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS'; export const GET_SHORT_URL_VISITS_LARGE = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_LARGE'; export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_CANCEL'; -/* eslint-enable padding-line-between-statements, newline-after-var */ +/* eslint-enable padding-line-between-statements */ export const shortUrlVisitsType = PropTypes.shape({ visits: PropTypes.array, @@ -23,45 +24,30 @@ const initialState = { cancelLoad: false, }; -export default function reducer(state = initialState, action) { - switch (action.type) { - case GET_SHORT_URL_VISITS_START: - return { - ...state, - loading: true, - loadingLarge: false, - cancelLoad: false, - }; - case GET_SHORT_URL_VISITS_ERROR: - return { - ...state, - loading: false, - loadingLarge: false, - error: true, - cancelLoad: false, - }; - case GET_SHORT_URL_VISITS: - return { - visits: action.visits, - loading: false, - loadingLarge: false, - error: false, - cancelLoad: false, - }; - case GET_SHORT_URL_VISITS_LARGE: - return { - ...state, - loadingLarge: true, - }; - case GET_SHORT_URL_VISITS_CANCEL: - return { - ...state, - cancelLoad: true, - }; - default: - return state; - } -} +export default handleActions({ + [GET_SHORT_URL_VISITS_START]: (state) => ({ + ...state, + loading: true, + loadingLarge: false, + cancelLoad: false, + }), + [GET_SHORT_URL_VISITS_ERROR]: (state) => ({ + ...state, + loading: false, + loadingLarge: false, + error: true, + cancelLoad: false, + }), + [GET_SHORT_URL_VISITS]: (state, { visits }) => ({ + visits, + loading: false, + loadingLarge: false, + error: false, + cancelLoad: false, + }), + [GET_SHORT_URL_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }), + [GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }), +}, initialState); export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) => async (dispatch, getState) => { dispatch({ type: GET_SHORT_URL_VISITS_START }); @@ -124,4 +110,4 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, dates) => } }; -export const cancelGetShortUrlVisits = () => ({ type: GET_SHORT_URL_VISITS_CANCEL }); +export const cancelGetShortUrlVisits = createAction(GET_SHORT_URL_VISITS_CANCEL); diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.js index 2316e583..423637d6 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.js @@ -9,9 +9,6 @@ import { RESET_SHORT_URL_PARAMS } from '../../../src/short-urls/reducers/shortUr describe('selectedServerReducer', () => { describe('reducer', () => { - it('returns default when action is not handled', () => - expect(reducer(null, { type: 'unknown' })).toEqual(null)); - it('returns default when action is RESET_SELECTED_SERVER', () => expect(reducer(null, { type: RESET_SELECTED_SERVER })).toEqual(null)); diff --git a/test/servers/reducers/server.test.js b/test/servers/reducers/server.test.js index 99cf30b7..cbf81d00 100644 --- a/test/servers/reducers/server.test.js +++ b/test/servers/reducers/server.test.js @@ -9,13 +9,13 @@ import reducer, { } from '../../../src/servers/reducers/server'; describe('serverReducer', () => { - const servers = { + const payload = { abc123: { id: 'abc123' }, def456: { id: 'def456' }, }; - const expectedFetchServersResult = { type: FETCH_SERVERS, servers }; + const expectedFetchServersResult = { type: FETCH_SERVERS, payload }; const ServersServiceMock = { - listServers: sinon.fake.returns(servers), + listServers: sinon.fake.returns(payload), createServer: sinon.fake(), deleteServer: sinon.fake(), createServers: sinon.fake(), @@ -23,10 +23,7 @@ describe('serverReducer', () => { describe('reducer', () => { it('returns servers when action is FETCH_SERVERS', () => - expect(reducer({}, { type: FETCH_SERVERS, servers })).toEqual(servers)); - - it('returns default when action is unknown', () => - expect(reducer({}, { type: 'unknown' })).toEqual({})); + expect(reducer({}, { type: FETCH_SERVERS, payload })).toEqual(payload)); }); describe('action creators', () => { @@ -79,7 +76,7 @@ describe('serverReducer', () => { describe('createServer', () => { it('creates multiple servers and then fetches servers again', () => { - const serversToCreate = values(servers); + const serversToCreate = values(payload); const result = createServers(ServersServiceMock, () => expectedFetchServersResult)(serversToCreate); expect(result).toEqual(expectedFetchServersResult); diff --git a/test/short-urls/reducers/shortUrlCreation.test.js b/test/short-urls/reducers/shortUrlCreation.test.js index 371d257f..c9a4e0e3 100644 --- a/test/short-urls/reducers/shortUrlCreation.test.js +++ b/test/short-urls/reducers/shortUrlCreation.test.js @@ -39,9 +39,6 @@ describe('shortUrlCreationReducer', () => { error: false, }); }); - - it('returns provided state on unknown action', () => - expect(reducer({}, { type: 'unknown' })).toEqual({})); }); describe('resetCreateShortUrl', () => { diff --git a/test/short-urls/reducers/shortUrlDeleteion.test.js b/test/short-urls/reducers/shortUrlDeletion.test.js similarity index 95% rename from test/short-urls/reducers/shortUrlDeleteion.test.js rename to test/short-urls/reducers/shortUrlDeletion.test.js index 60aca8e7..b0289614 100644 --- a/test/short-urls/reducers/shortUrlDeleteion.test.js +++ b/test/short-urls/reducers/shortUrlDeletion.test.js @@ -45,12 +45,6 @@ describe('shortUrlDeletionReducer', () => { errorData, }); }); - - it('returns provided state as is on unknown action', () => { - const state = { foo: 'bar' }; - - expect(reducer(state, { type: 'unknown' })).toEqual(state); - }); }); describe('resetDeleteShortUrl', () => { diff --git a/test/short-urls/reducers/shortUrlsList.test.js b/test/short-urls/reducers/shortUrlsList.test.js index 08274afb..a3055a04 100644 --- a/test/short-urls/reducers/shortUrlsList.test.js +++ b/test/short-urls/reducers/shortUrlsList.test.js @@ -70,12 +70,6 @@ describe('shortUrlsListReducer', () => { }, }); }); - - it('returns provided state as is on unknown action', () => { - const state = { foo: 'bar' }; - - expect(reducer(state, { type: 'unknown' })).toEqual(state); - }); }); describe('listShortUrls', () => { diff --git a/test/short-urls/reducers/shortUrlsListParams.test.js b/test/short-urls/reducers/shortUrlsListParams.test.js index 334fdb71..5c032e95 100644 --- a/test/short-urls/reducers/shortUrlsListParams.test.js +++ b/test/short-urls/reducers/shortUrlsListParams.test.js @@ -8,9 +8,6 @@ describe('shortUrlsListParamsReducer', () => { describe('reducer', () => { const defaultState = { page: '1' }; - it('returns default value when action is unknown', () => - expect(reducer(defaultState, { type: 'unknown' })).toEqual(defaultState)); - it('returns params when action is LIST_SHORT_URLS', () => expect(reducer(defaultState, { type: LIST_SHORT_URLS, params: { searchTerm: 'foo' } })).toEqual({ ...defaultState, diff --git a/test/tags/reducers/tagDelete.test.js b/test/tags/reducers/tagDelete.test.js index b3cc8c8e..8915b14f 100644 --- a/test/tags/reducers/tagDelete.test.js +++ b/test/tags/reducers/tagDelete.test.js @@ -30,9 +30,6 @@ describe('tagDeleteReducer', () => { error: false, }); }); - - it('returns provided state on unknown action', () => - expect(reducer({}, { type: 'unknown' })).toEqual({})); }); describe('tagDeleted', () => { diff --git a/test/tags/reducers/tagEdit.test.js b/test/tags/reducers/tagEdit.test.js index 91642c06..14a63353 100644 --- a/test/tags/reducers/tagEdit.test.js +++ b/test/tags/reducers/tagEdit.test.js @@ -32,9 +32,6 @@ describe('tagEditReducer', () => { newName: 'bar', }); }); - - it('returns provided state on unknown action', () => - expect(reducer({}, { type: 'unknown' })).toEqual({})); }); describe('tagEdited', () => { diff --git a/test/visits/reducers/shortUrlDetail.test.js b/test/visits/reducers/shortUrlDetail.test.js index 2a5de1e1..218ca1c3 100644 --- a/test/visits/reducers/shortUrlDetail.test.js +++ b/test/visits/reducers/shortUrlDetail.test.js @@ -32,17 +32,6 @@ describe('shortUrlDetailReducer', () => { expect(error).toEqual(false); expect(shortUrl).toEqual(actionShortUrl); }); - - it('returns default state on unknown action', () => { - const defaultState = { - shortUrl: {}, - loading: false, - error: false, - }; - const state = reducer(defaultState, { type: 'unknown' }); - - expect(state).toEqual(defaultState); - }); }); describe('getShortUrlDetail', () => { diff --git a/test/visits/reducers/shortUrlVisits.test.js b/test/visits/reducers/shortUrlVisits.test.js index 97978609..0f714675 100644 --- a/test/visits/reducers/shortUrlVisits.test.js +++ b/test/visits/reducers/shortUrlVisits.test.js @@ -49,17 +49,6 @@ describe('shortUrlVisitsReducer', () => { expect(error).toEqual(false); expect(visits).toEqual(actionVisits); }); - - it('returns default state on unknown action', () => { - const defaultState = { - visits: [], - loading: false, - error: false, - }; - const state = reducer(defaultState, { type: 'unknown' }); - - expect(state).toEqual(defaultState); - }); }); describe('getShortUrlVisits', () => { diff --git a/yarn.lock b/yarn.lock index 44d72b70..639969e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5663,6 +5663,11 @@ jsx-ast-utils@^2.0.1: dependencies: array-includes "^3.0.3" +just-curry-it@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" + integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg== + just-extend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc" @@ -7612,6 +7617,11 @@ postcss-reduce-initial@^4.0.2: postcss-reduce-transforms@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561" + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" postcss-replace-overflow-wrap@^3.0.0: version "3.0.0" @@ -8325,6 +8335,22 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" +reduce-reducers@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c" + integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw== + +redux-actions@^2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e" + integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw== + dependencies: + invariant "^2.2.4" + just-curry-it "^3.1.0" + loose-envify "^1.4.0" + reduce-reducers "^0.4.3" + to-camel-case "^1.0.0" + redux-thunk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" @@ -8936,6 +8962,11 @@ shebang-regex@^1.0.0: shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" shellwords@^0.1.1: version "0.1.1" @@ -9676,6 +9707,13 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" +to-camel-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" + integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY= + dependencies: + to-space-case "^1.0.0" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -9684,6 +9722,11 @@ to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -9706,6 +9749,13 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= + dependencies: + to-no-case "^1.0.0" + toggle-selection@^1.0.3: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"