From dd9ee044ebe5aad846b1e36d44e996589ff0762b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 19 Nov 2022 09:29:29 +0100 Subject: [PATCH] Ensured JSON decodedoes not happen for endpoints returning empty body --- src/api/services/ShlinkApiClient.ts | 6 +-- src/common/services/HttpClient.ts | 10 +++-- src/tags/helpers/EditTagModal.tsx | 7 ++-- src/tags/reducers/tagEdit.ts | 59 ++++++++++++++-------------- src/tags/reducers/tagsList.ts | 16 ++++---- src/tags/services/provideServices.ts | 6 +-- 6 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/api/services/ShlinkApiClient.ts b/src/api/services/ShlinkApiClient.ts index 66d36332..14d0582f 100644 --- a/src/api/services/ShlinkApiClient.ts +++ b/src/api/services/ShlinkApiClient.ts @@ -79,7 +79,7 @@ export class ShlinkApiClient { this.performRequest(`/short-urls/${shortCode}`, 'GET', { domain }); public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise => - this.performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain }) + this.performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain }) .then(() => {}); public readonly updateShortUrl = async ( @@ -95,11 +95,11 @@ export class ShlinkApiClient { .then(({ data, stats }) => ({ tags: data, stats })); public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> => - this.performRequest('/tags', 'DELETE', { tags }) + this.performRequest('/tags', 'DELETE', { tags }) .then(() => ({ tags })); public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> => - this.performRequest('/tags', 'PUT', {}, { oldName, newName }) + this.performRequest('/tags', 'PUT', {}, { oldName, newName }) .then(() => ({ oldName, newName })); public readonly health = async (): Promise => this.performRequest('/health', 'GET'); diff --git a/src/common/services/HttpClient.ts b/src/common/services/HttpClient.ts index bd9b47f9..7e5d589d 100644 --- a/src/common/services/HttpClient.ts +++ b/src/common/services/HttpClient.ts @@ -5,13 +5,15 @@ export class HttpClient { public fetchJson(url: string, options?: RequestInit): Promise { return this.fetch(url, options).then(async (resp) => { - const parsed = await resp.json(); - if (!resp.ok) { - throw parsed; + throw await resp.json(); } - return parsed as T; + try { + return (await resp.json()) as T; + } catch (e) { + return undefined as T; + } }); } diff --git a/src/tags/helpers/EditTagModal.tsx b/src/tags/helpers/EditTagModal.tsx index 59297b6c..06dbefe5 100644 --- a/src/tags/helpers/EditTagModal.tsx +++ b/src/tags/helpers/EditTagModal.tsx @@ -27,9 +27,10 @@ export const EditTagModal = ({ getColorForKey }: ColorGenerator) => ( const [showColorPicker, toggleColorPicker, , hideColorPicker] = useToggle(); const { editing, error, edited, errorData } = tagEdit; const saveTag = handleEventPreventingDefault( - async () => editTag({ oldName: tag, newName: newTagName, color }) - .then(toggle) - .catch(() => {}), + async () => { + await editTag({ oldName: tag, newName: newTagName, color }); + toggle(); + }, ); const onClosed = pipe(hideColorPicker, () => edited && tagEdited({ oldName: tag, newName: newTagName, color })); diff --git a/src/tags/reducers/tagEdit.ts b/src/tags/reducers/tagEdit.ts index 7c32b3ee..675f8898 100644 --- a/src/tags/reducers/tagEdit.ts +++ b/src/tags/reducers/tagEdit.ts @@ -33,35 +33,34 @@ const initialState: TagEdition = { export const tagEdited = createAction(`${REDUCER_PREFIX}/tagEdited`); -export const tagEditReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder, colorGenerator: ColorGenerator) => { - const editTag = createAsyncThunk( - `${REDUCER_PREFIX}/editTag`, - async ({ oldName, newName, color }: EditTag, { getState }): Promise => { - await buildShlinkApiClient(getState).editTag(oldName, newName); - colorGenerator.setColorForKey(newName, color); +export const editTag = ( + buildShlinkApiClient: ShlinkApiClientBuilder, + colorGenerator: ColorGenerator, +) => createAsyncThunk( + `${REDUCER_PREFIX}/editTag`, + async ({ oldName, newName, color }: EditTag, { getState }): Promise => { + await buildShlinkApiClient(getState).editTag(oldName, newName); + colorGenerator.setColorForKey(newName, color); - return { oldName, newName, color }; - }, - ); + return { oldName, newName, color }; + }, +); - const { reducer } = createSlice({ - name: REDUCER_PREFIX, - initialState, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(editTag.pending, () => ({ editing: true, edited: false, error: false })); - builder.addCase( - editTag.rejected, - (_, { error }) => ({ editing: false, edited: false, error: true, errorData: parseApiError(error) }), - ); - builder.addCase(editTag.fulfilled, (_, { payload }) => ({ - ...pick(['oldName', 'newName'], payload), - editing: false, - edited: true, - error: false, - })); - }, - }); - - return { reducer, editTag }; -}; +export const tagEditReducerCreator = (editTagThunk: ReturnType) => createSlice({ + name: REDUCER_PREFIX, + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(editTagThunk.pending, () => ({ editing: true, edited: false, error: false })); + builder.addCase( + editTagThunk.rejected, + (_, { error }) => ({ editing: false, edited: false, error: true, errorData: parseApiError(error) }), + ); + builder.addCase(editTagThunk.fulfilled, (_, { payload }) => ({ + ...pick(['oldName', 'newName'], payload), + editing: false, + edited: true, + error: false, + })); + }, +}); diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index da15e123..7914421c 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -111,15 +111,15 @@ export const tagsListReducerCreator = ( { ...initialState, stats: payload.stats, tags: payload.tags, filteredTags: payload.tags } )); - builder.addCase(tagDeleted, (state, { payload: tag }) => ({ - ...state, - tags: rejectTag(state.tags, tag), - filteredTags: rejectTag(state.filteredTags, tag), + builder.addCase(tagDeleted, ({ tags, filteredTags, ...rest }, { payload: tag }) => ({ + ...rest, + tags: rejectTag(tags, tag), + filteredTags: rejectTag(filteredTags, tag), })); - builder.addCase(tagEdited, (state, { payload }) => ({ - ...state, - tags: state.tags.map(renameTag(payload.oldName, payload.newName)).sort(), - filteredTags: state.filteredTags.map(renameTag(payload.oldName, payload.newName)).sort(), + builder.addCase(tagEdited, ({ tags, filteredTags, ...rest }, { payload }) => ({ + ...rest, + tags: tags.map(renameTag(payload.oldName, payload.newName)).sort(), + filteredTags: filteredTags.map(renameTag(payload.oldName, payload.newName)).sort(), })); builder.addCase(createNewVisits, (state, { payload }) => ({ ...state, diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index 92980f72..2d81afb7 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -7,7 +7,7 @@ import { EditTagModal } from '../helpers/EditTagModal'; import { TagsList } from '../TagsList'; import { filterTags, listTags, tagsListReducerCreator } from '../reducers/tagsList'; import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete'; -import { tagEdited, tagEditReducerCreator } from '../reducers/tagEdit'; +import { editTag, tagEdited, tagEditReducerCreator } from '../reducers/tagEdit'; import { ConnectDecorator } from '../../container/types'; import { TagsCards } from '../TagsCards'; import { TagsTable } from '../TagsTable'; @@ -38,7 +38,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { )); // Reducers - bottle.serviceFactory('tagEditReducerCreator', tagEditReducerCreator, 'buildShlinkApiClient', 'ColorGenerator'); + bottle.serviceFactory('tagEditReducerCreator', tagEditReducerCreator, 'editTag'); bottle.serviceFactory('tagEditReducer', prop('reducer'), 'tagEditReducerCreator'); bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient'); @@ -58,7 +58,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('deleteTag', prop('deleteTag'), 'tagDeleteReducerCreator'); bottle.serviceFactory('tagDeleted', () => tagDeleted); - bottle.serviceFactory('editTag', prop('editTag'), 'tagEditReducerCreator'); + bottle.serviceFactory('editTag', editTag, 'buildShlinkApiClient', 'ColorGenerator'); bottle.serviceFactory('tagEdited', () => tagEdited); };