From 5435ce93ee6d6c3f0618a2d37f18b6c016f81495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20Miesi=C4=85c?= <tadeusz.miesiac@gmail.com> Date: Wed, 27 Sep 2023 16:04:50 +0200 Subject: [PATCH] feat(async redux example): added template for async communication with redux thunk --- next-env.d.ts | 1 + package-lock.json | 180 ++++++++++++++++++++++-- package.json | 4 +- pages/{redux.tsx => redux-api-poc.tsx} | 7 +- src/constants/.gitkeep | 0 src/constants/api.ts | 1 + src/models/disease.ts | 9 ++ src/models/organism.ts | 9 ++ src/models/project.ts | 47 +++++++ src/redux/project/project.reducers.ts | 10 ++ src/redux/project/project.slice.ts | 20 +++ src/redux/project/project.thunks.ts | 16 +++ src/redux/project/project.types.ts | 7 + src/redux/store.ts | 4 +- src/services/.gitkeep | 0 src/services/api/utils/axiosInstance.ts | 6 + src/services/api/utils/useApiQuery.ts | 39 +++++ src/types/.gitkeep | 0 src/types/api.ts | 18 +++ src/utils/.gitkeep | 0 src/utils/validateDataUsingZodSchema.ts | 15 ++ 21 files changed, 378 insertions(+), 15 deletions(-) rename pages/{redux.tsx => redux-api-poc.tsx} (69%) delete mode 100644 src/constants/.gitkeep create mode 100644 src/constants/api.ts create mode 100644 src/models/disease.ts create mode 100644 src/models/organism.ts create mode 100644 src/models/project.ts create mode 100644 src/redux/project/project.reducers.ts create mode 100644 src/redux/project/project.slice.ts create mode 100644 src/redux/project/project.thunks.ts create mode 100644 src/redux/project/project.types.ts delete mode 100644 src/services/.gitkeep create mode 100644 src/services/api/utils/axiosInstance.ts create mode 100644 src/services/api/utils/useApiQuery.ts delete mode 100644 src/types/.gitkeep create mode 100644 src/types/api.ts delete mode 100644 src/utils/.gitkeep create mode 100644 src/utils/validateDataUsingZodSchema.ts diff --git a/next-env.d.ts b/next-env.d.ts index 53e1f337..fd36f949 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,4 +1,5 @@ /// <reference types="next" /> +/// <reference types="next/image-types/global" /> /// <reference types="next/navigation-types/compat/navigation" /> // NOTE: This file should not be edited diff --git a/package-lock.json b/package-lock.json index 02f72268..1a168c99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.15", + "axios": "^1.5.1", "eslint-config-next": "13.4.19", "next": "13.4.19", "postcss": "8.4.29", @@ -22,7 +23,8 @@ "react-dom": "18.2.0", "react-redux": "^8.1.2", "tailwind-merge": "^1.14.0", - "tailwindcss": "3.3.3" + "tailwindcss": "3.3.3", + "zod": "^3.22.2" }, "devDependencies": { "@commitlint/cli": "^17.7.1", @@ -1966,6 +1968,21 @@ "resolved": "https://registry.npmjs.org/@next/font/-/font-13.5.2.tgz", "integrity": "sha512-c9EXqdXMEErMLrC71wZvpcOnNVkEEufZOO3EjgQJcKQUwPISvnkgIj9GKFIop0rX2dLNdzL3OC/4nrcAqWqUsg==" }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", + "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-darwin-x64": { "version": "13.4.19", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", @@ -1981,6 +1998,111 @@ "node": ">= 10" } }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", + "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", + "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", + "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", + "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", + "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", + "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.19", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", + "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3043,8 +3165,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -3125,6 +3246,21 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -3839,7 +3975,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4845,7 +4980,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -6262,6 +6396,25 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -6283,7 +6436,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -9391,7 +9543,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -9400,7 +9551,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -9586,6 +9736,14 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/next/node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12641,9 +12799,9 @@ } }, "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", + "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 04cdab67..30e182e0 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.15", + "axios": "^1.5.1", "eslint-config-next": "13.4.19", "next": "13.4.19", "postcss": "8.4.29", @@ -40,7 +41,8 @@ "react-dom": "18.2.0", "react-redux": "^8.1.2", "tailwind-merge": "^1.14.0", - "tailwindcss": "3.3.3" + "tailwindcss": "3.3.3", + "zod": "^3.22.2" }, "devDependencies": { "@commitlint/cli": "^17.7.1", diff --git a/pages/redux.tsx b/pages/redux-api-poc.tsx similarity index 69% rename from pages/redux.tsx rename to pages/redux-api-poc.tsx index e8bd8714..4a195e6e 100644 --- a/pages/redux.tsx +++ b/pages/redux-api-poc.tsx @@ -1,15 +1,18 @@ -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import { selectSearchValue } from '@/redux/search/search.selectors'; import { setSearchValue } from '@/redux/search/search.slice'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { getProjectById } from '@/redux/project/project.thunks'; const ReduxPage = (): JSX.Element => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const searchValue = useSelector(selectSearchValue); const triggerSyncUpdate = (): void => { // eslint-disable-next-line prefer-template const newValue = searchValue + 'test'; dispatch(setSearchValue(newValue)); + dispatch(getProjectById('pd_map_winter_23')); }; return ( diff --git a/src/constants/.gitkeep b/src/constants/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/constants/api.ts b/src/constants/api.ts new file mode 100644 index 00000000..f3ebe0ec --- /dev/null +++ b/src/constants/api.ts @@ -0,0 +1 @@ +export const BASE_API_URL = 'https://corsproxy.io/?https://pdmap.uni.lu/minerva/api'; diff --git a/src/models/disease.ts b/src/models/disease.ts new file mode 100644 index 00000000..65079e66 --- /dev/null +++ b/src/models/disease.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const disease = z.object({ + link: z.string(), + type: z.string(), + resource: z.string(), + id: z.number(), + annotatorClassName: z.string(), +}); diff --git a/src/models/organism.ts b/src/models/organism.ts new file mode 100644 index 00000000..4b003eef --- /dev/null +++ b/src/models/organism.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const organism = z.object({ + link: z.string(), + type: z.string(), + resource: z.string(), + id: z.number(), + annotatorClassName: z.string(), +}); diff --git a/src/models/project.ts b/src/models/project.ts new file mode 100644 index 00000000..051e5ca1 --- /dev/null +++ b/src/models/project.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; +import { disease } from './disease'; +import { organism } from './organism'; + +export const projectSchema = z.object({ + version: z.string(), + disease, + organism, + idObject: z.number(), + status: z.string(), + directory: z.string(), + progress: z.number(), + notifyEmail: z.string(), + logEntries: z.boolean(), + name: z.string(), + sharedInMinervaNet: z.boolean(), + owner: z.string(), + projectId: z.string(), + creationDate: z.string(), + mapCanvasType: z.string(), + overviewImageViews: z.array( + z.object({ + idObject: z.number(), + filename: z.string(), + width: z.number(), + height: z.number(), + links: z.array( + z.union([ + z.object({ + idObject: z.number(), + polygon: z.array(z.object({ x: z.number(), y: z.number() })), + imageLinkId: z.number(), + type: z.string(), + }), + z.object({ + idObject: z.number(), + polygon: z.array(z.object({ x: z.number(), y: z.number() })), + zoomLevel: z.number(), + modelPoint: z.object({ x: z.number(), y: z.number() }), + modelLinkId: z.number(), + type: z.string(), + }), + ]), + ), + }), + ), +}); diff --git a/src/redux/project/project.reducers.ts b/src/redux/project/project.reducers.ts new file mode 100644 index 00000000..aee885ae --- /dev/null +++ b/src/redux/project/project.reducers.ts @@ -0,0 +1,10 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { ProjectState } from '@/redux/project/project.types'; +import { getProjectById } from '@/redux/project/project.thunks'; + +export const getProjectByIdReducer = (builder: ActionReducerMapBuilder<ProjectState>): void => { + builder.addCase(getProjectById.fulfilled, (state, action) => { + state.data = action.payload; + state.loading = 'succeeded'; + }); +}; diff --git a/src/redux/project/project.slice.ts b/src/redux/project/project.slice.ts new file mode 100644 index 00000000..994cb484 --- /dev/null +++ b/src/redux/project/project.slice.ts @@ -0,0 +1,20 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { ProjectState } from '@/redux/project/project.types'; +import { getProjectByIdReducer } from './project.reducers'; + +const initialState: ProjectState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +const projectSlice = createSlice({ + name: 'project', + initialState, + reducers: {}, + extraReducers: builder => { + getProjectByIdReducer(builder); + }, +}); + +export default projectSlice.reducer; diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts new file mode 100644 index 00000000..d26bbdda --- /dev/null +++ b/src/redux/project/project.thunks.ts @@ -0,0 +1,16 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { Project } from '@/types/api'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { projectSchema } from '@/models/project'; + +export const getProjectById = createAsyncThunk( + 'project/getUsersByIdStatus', + async (id: string): Promise<Project | undefined> => { + const response = await axiosInstance.get<Project>(`projects/${id}`); + + const isDataValid = validateDataUsingZodSchema(response.data, projectSchema); + + return isDataValid ? response.data : undefined; + }, +); diff --git a/src/redux/project/project.types.ts b/src/redux/project/project.types.ts new file mode 100644 index 00000000..b5ace9d7 --- /dev/null +++ b/src/redux/project/project.types.ts @@ -0,0 +1,7 @@ +import { Project } from '@/types/api'; + +export type ProjectState = { + data: Project | undefined | []; + loading: 'idle' | 'pending' | 'succeeded' | 'failed'; + error: Error; +}; diff --git a/src/redux/store.ts b/src/redux/store.ts index 5d76dbde..d1335018 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,9 +1,11 @@ import { configureStore } from '@reduxjs/toolkit'; -import searchReducer from './search/search.slice'; +import searchReducer from '@/redux/search/search.slice'; +import projectSlice from '@/redux/project/project.slice'; export const store = configureStore({ reducer: { search: searchReducer, + project: projectSlice, }, devTools: true, }); diff --git a/src/services/.gitkeep b/src/services/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/services/api/utils/axiosInstance.ts b/src/services/api/utils/axiosInstance.ts new file mode 100644 index 00000000..5e50fba7 --- /dev/null +++ b/src/services/api/utils/axiosInstance.ts @@ -0,0 +1,6 @@ +import axios from 'axios'; +import { BASE_API_URL } from '@/constants/api'; + +export const axiosInstance = axios.create({ + baseURL: BASE_API_URL, +}); diff --git a/src/services/api/utils/useApiQuery.ts b/src/services/api/utils/useApiQuery.ts new file mode 100644 index 00000000..d150d656 --- /dev/null +++ b/src/services/api/utils/useApiQuery.ts @@ -0,0 +1,39 @@ +import { BASE_API_URL } from '@/constants/api'; +import { QueryOptions } from '@/types/api'; + +import useAxios, { UseAxiosResult } from 'axios-hooks'; +import { useMemo } from 'react'; +import { AnyZodObject, ZodError } from 'zod'; + +type UseApiQuery = <TResponse extends AnyZodObject>( + queryOptions: QueryOptions<TResponse>, +) => UseAxiosResult<TResponse>; + +export const useApiQuery: UseApiQuery = <TResponse extends AnyZodObject>({ + method, + path, + response, +}: QueryOptions<TResponse>) => { + const [{ data: fetchData, loading, error }, refetch, cancelRequest] = useAxios<TResponse>({ + method, + url: `${BASE_API_URL}${path}`, + }); + + const dataValidation: { success: boolean; error?: ZodError<TResponse> } = useMemo(() => { + if (!fetchData) { + return { success: false, error: undefined }; + } + + return { + error: undefined, + ...response.safeParse(fetchData), + }; + }, [fetchData, response]); + + const data = useMemo( + () => (dataValidation.success ? fetchData : undefined), + [dataValidation.success, fetchData], + ); + + return [{ data, loading, error }, refetch, cancelRequest]; +}; diff --git a/src/types/.gitkeep b/src/types/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 00000000..98a13076 --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; +import { disease } from '@/models/disease'; +import { organism } from '@/models/organism'; +import { projectSchema } from '@/models/project'; + +export interface QueryOptions<Response> { + method: 'GET' | 'POST'; + path: string; + response: Response; +} + +export interface Query<Params, Response> { + (params: Params): QueryOptions<Response>; +} + +export type Project = z.infer<typeof projectSchema>; +export type Organism = z.infer<typeof organism>; +export type Disease = z.infer<typeof disease>; diff --git a/src/utils/.gitkeep b/src/utils/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/utils/validateDataUsingZodSchema.ts b/src/utils/validateDataUsingZodSchema.ts new file mode 100644 index 00000000..a7b0cf08 --- /dev/null +++ b/src/utils/validateDataUsingZodSchema.ts @@ -0,0 +1,15 @@ +import { ZodSchema } from 'zod'; + +type IsApiResponseValid = <TData>(data: TData, schema: ZodSchema) => boolean; + +export const validateDataUsingZodSchema: IsApiResponseValid = (data, schema: ZodSchema) => { + const validationResults = schema.safeParse(data); + + if (validationResults.success === false) { + // TODO - probably need to rething way of handling parsing errors, for now let's leave it to console.log + // eslint-disable-next-line no-console + console.error('Error on parsing data', validationResults.error); + } + + return validationResults.success; +}; -- GitLab