diff --git a/.eslintignore b/.eslintignore index 9c30e8e44376c66e72229082fd69fd971524aa06..465054d8cb71842d55437e2fbdab15b12b3e0558 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,5 @@ # Auto generated by React serviceWorker.* -setupTests.* \ No newline at end of file +setupTests.* +setupProxy.* \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 5803798cdc303788a1ef350c8a4e9e93ad79fd18..f1281fe4ed12edeaf22626d69001e31d8a896500 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,8 @@ module.exports = { rules: { 'react/prop-types': 0, - '@typescript-eslint/explicit-function-return-type': 2, + '@typescript-eslint/explicit-function-return-type': 1, 'object-curly-newline': 0, + 'implicit-arrow-linebreak': 0, }, }; diff --git a/package-lock.json b/package-lock.json index fa2b8b6f56e084cbc2819d3c6385790ab2b843cc..1809a4e2106aef7b676b8dd119da39e14f77a134 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2940,6 +2940,14 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@types/http-proxy": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz", + "integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==", + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -8563,14 +8571,55 @@ } }, "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.0.5.tgz", + "integrity": "sha512-CKzML7u4RdGob8wuKI//H8Ein6wNTEQR7yjVEzPbhBLGdOfkfvgTnp2HLnniKBDP9QW4eG10/724iTWLBeER3g==", "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "@types/http-proxy": "^1.17.4", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.19", + "micromatch": "^4.0.2" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } } }, "http-signature": { @@ -16914,6 +16963,17 @@ } } }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", diff --git a/package.json b/package.json index a6c602e19f26c11f88c4f3a168be097b576d9f4c..337d77d817d78cf3920f2b1503b53dbf8d31c583 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "dependencies": { "@material-ui/core": "^4.11.0", "axios": "^0.20.0", + "http-proxy-middleware": "^1.0.5", "react": "^16.13.1", "react-dom": "^16.13.1", "react-hook-form": "^6.5.3", diff --git a/src/core/App.tsx b/src/core/App.tsx index ebed2efd7cd3c9fe7e9dc87e9c9f96982eac1d14..f9243ddba1c4f5508331442f87cb184322ba0c32 100644 --- a/src/core/App.tsx +++ b/src/core/App.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import styled from 'styled-components'; +import useAddNews from '../hooks/useAddNews'; import Footer from './components/Footer'; import Header from './components/Header'; import MainRouting from './components/MainRouting'; @@ -16,7 +17,19 @@ const MainContent = styled.div` height: 100%; `; -function App() { +function App(): React.ReactElement { + const [response, addNews] = useAddNews(); + + useEffect(() => { + addNews({ body: { title: 'Testing', text: 'Test Test TEST' } }); + }, [addNews]); + + useEffect(() => { + if (response.data) { + console.log(response.data.text); + } + }, [response.data]); + return ( <Router> <Container> diff --git a/src/hooks/types.ts b/src/hooks/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..d639a344b70e94866b43d4923f40304dcbb6421c --- /dev/null +++ b/src/hooks/types.ts @@ -0,0 +1,29 @@ +import { AxiosPromise } from 'axios'; + +export interface ResponseData<T> { + isLoading: boolean; + isError: boolean; + data?: T; +} + +export interface RequestArgs<T = any> { + request: ApiRequest<T>; + initialData?: T; + initialParams?: RequestParams; +} + +export interface RequestParams { + body?: any; + params?: any; + args?: any; +} + +export type ApiRequest<T> = (params: RequestParams) => AxiosPromise<T>; + +export type Refetch = (params?: RequestParams) => void; + +export interface INews { + title: string; + text: string; + publishedAt: string; +} diff --git a/src/hooks/useAddNews.ts b/src/hooks/useAddNews.ts new file mode 100644 index 0000000000000000000000000000000000000000..0afeea1a19ca924ff4bee70407b2c9e92f41a9ff --- /dev/null +++ b/src/hooks/useAddNews.ts @@ -0,0 +1,12 @@ +import { INews } from './types'; +import useRequest from './useRequest'; +import userRestQueries from './useRestQueries'; + +const useAddNews = () => { + const { post } = userRestQueries(); + const request = post<INews>('/api/v1/news'); + + return useRequest<INews>({ request }); +}; + +export default useAddNews; diff --git a/src/hooks/useGetNewsList.ts b/src/hooks/useGetNewsList.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cca43ad421fba11731dd1aad220f0ac83dd0af2 --- /dev/null +++ b/src/hooks/useGetNewsList.ts @@ -0,0 +1,12 @@ +import { INews } from './types'; +import useRequest from './useRequest'; +import userRestQueries from './useRestQueries'; + +const useGetNewsList = (data?: INews[]) => { + const { get } = userRestQueries(); + const request = get<INews[]>('/api/v1/news'); + + return useRequest<INews[]>({ request, initialData: data }); +}; + +export default useGetNewsList; diff --git a/src/hooks/useRequest.ts b/src/hooks/useRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e41e58f6add946cf3015c953537ab50a9879353 --- /dev/null +++ b/src/hooks/useRequest.ts @@ -0,0 +1,40 @@ +import { useCallback, useEffect, useState } from 'react'; +import { Refetch, RequestArgs, ResponseData } from './types'; + +function useRequest<T = any>({ request, initialData }: RequestArgs<T>): [ResponseData<T>, Refetch] { + const [data, setData] = useState(initialData); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [fetching, setFetching] = useState(false); + const [requestParams, setRequestParams] = useState({}); + + const refetch = useCallback((newParams?) => { + setRequestParams(newParams ?? {}); + setFetching(true); + }, []); + + useEffect(() => { + const fetchData = async () => { + setIsError(false); + setIsLoading(true); + + try { + const result = await request(requestParams); + + setData(result.data); + } catch (error) { + setIsError(true); + } + setFetching(false); + setIsLoading(false); + }; + + if (fetching && !isLoading) { + fetchData(); + } + }, [request, fetching, isLoading, requestParams]); + + return [{ data, isLoading, isError }, refetch]; +} + +export default useRequest; diff --git a/src/hooks/useRestQueries.ts b/src/hooks/useRestQueries.ts new file mode 100644 index 0000000000000000000000000000000000000000..1201e5c74ff6438a529af36d11dd6d789719be31 --- /dev/null +++ b/src/hooks/useRestQueries.ts @@ -0,0 +1,20 @@ +import axios, { AxiosRequestConfig } from 'axios'; +import { ApiRequest, RequestParams } from './types'; + +function userRestQueries( + // When a request needs more config like CancelToken, etc. + config?: AxiosRequestConfig, +): Record<string, <Data>(path: string) => ApiRequest<Data>> { + return { + get: <Data>(path: string) => (params: RequestParams) => + axios.get<Data>(path, { ...config, ...params }), + post: <Data>(path: string) => (params: RequestParams) => + axios.post<Data>(path, params.body ?? {}, { ...config, ...params }), + put: <Data>(path: string) => (params: RequestParams) => + axios.put<Data>(path, params.body ?? {}, { ...config, ...params }), + delete: (path: string) => (params: RequestParams) => + axios.delete(path, { ...config, ...params }), + }; +} + +export default userRestQueries; diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 0000000000000000000000000000000000000000..a4b1c75f070cdcf2248b8bdef529c9b826bc4cb3 --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,11 @@ +const { createProxyMiddleware } = require('http-proxy-middleware'); + +module.exports = function (app) { + app.use( + '/api/v1', + createProxyMiddleware({ + target: 'http://localhost:8000', + changeOrigin: true, + }), + ); +};