From fbb2b11175d394929fe7209be342e2513d2dd064 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Fri, 25 Dec 2020 21:39:54 -0800 Subject: [PATCH] Introduce react-query --- packages/webapp/package.json | 18 ++- packages/webapp/src/app.tsx | 12 +- .../src/components/login-page/index.tsx | 8 +- .../src/components/maps-page/ActionDialog.tsx | 64 ++++++---- .../webapp/src/components/maps-page/index.tsx | 52 +++++--- packages/webapp/src/index.tsx | 8 +- packages/webapp/src/reducers/mapsListSlice.ts | 113 ------------------ packages/webapp/src/reducers/serviceSlice.ts | 58 +++++++++ packages/webapp/src/services/Service.ts | 47 ++++---- packages/webapp/src/store.ts | 18 +-- packages/webapp/src/tsconfig.json | 2 +- packages/webapp/tsconfig.json | 2 +- 12 files changed, 182 insertions(+), 220 deletions(-) delete mode 100644 packages/webapp/src/reducers/mapsListSlice.ts create mode 100644 packages/webapp/src/reducers/serviceSlice.ts diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 7833a9c1..721abb41 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -17,9 +17,6 @@ "@babel/preset-env": "^7.12.7", "@babel/preset-react": "^7.12.7", "@formatjs/cli": "^2.13.15", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", - "@types/react-router-dom": "^5.1.6", "@typescript-eslint/eslint-plugin": "^4.8.1", "@typescript-eslint/parser": "^4.8.1", "babel-loader": "^8.2.2", @@ -49,14 +46,15 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", "@reduxjs/toolkit": "^1.5.0", - "@types/axios": "^0.14.0", - "@types/react-google-recaptcha": "^2.1.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "axios": "^0.14.0", + "react": "^17.0.0", + "react-dom": "^17.0.0", "react-google-recaptcha": "^2.1.0", - "react-intl": "^5.10.6", + "react-intl": "^3.0.0", + "react-query": "^3.5.5", "react-redux": "^7.2.2", + "react-router": "^5.1.8", "react-router-dom": "^5.2.0", - "styled-components": "^5.2.1" + "styled-components": "^5.1.7" } -} \ No newline at end of file +} diff --git a/packages/webapp/src/app.tsx b/packages/webapp/src/app.tsx index 5f0706ad..57a37420 100644 --- a/packages/webapp/src/app.tsx +++ b/packages/webapp/src/app.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { IntlProvider } from 'react-intl' +import { IntlProvider } from 'react-intl'; +import { Route, Switch, Redirect, BrowserRouter as Router } from 'react-router-dom'; import { GlobalStyle } from './theme/global-style'; import RegistrationSuccessPage from './components/registration-success-page'; @@ -7,14 +8,7 @@ import ForgotPasswordSuccessPage from './components/forgot-password-success-page import RegistationPage from './components/registration-page'; import LoginPage from './components/login-page'; import MapsPage from './components/maps-page'; -import store from "./store" - -import { - Route, - Switch, - Redirect, - BrowserRouter as Router, -} from 'react-router-dom'; +import store from "./store"; import { ForgotPasswordPage } from './components/forgot-password-page'; import { Provider } from 'react-redux'; diff --git a/packages/webapp/src/components/login-page/index.tsx b/packages/webapp/src/components/login-page/index.tsx index 9befe644..0a71cf8a 100644 --- a/packages/webapp/src/components/login-page/index.tsx +++ b/packages/webapp/src/components/login-page/index.tsx @@ -2,18 +2,18 @@ import React, { useEffect } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Link } from 'react-router-dom' -import {PageContent} from '../../theme/global-style'; +import { PageContent } from '../../theme/global-style'; import FormErrorDialog from '../form-error-dialog' import Header from '../header' import Footer from '../footer' import SubmitButton from '../submit-button' - + const ConfigStatusMessage = (props: any) => { const enabled = props.enabled - let result = null; + let result; if (enabled === true) { result = (

@@ -21,7 +21,7 @@ const ConfigStatusMessage = (props: any) => {

); } - return result; + return result ? result : null; } const LoginError = () => { diff --git a/packages/webapp/src/components/maps-page/ActionDialog.tsx b/packages/webapp/src/components/maps-page/ActionDialog.tsx index 986a7cfd..69b42d82 100644 --- a/packages/webapp/src/components/maps-page/ActionDialog.tsx +++ b/packages/webapp/src/components/maps-page/ActionDialog.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; @@ -9,13 +9,11 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { ErrorInfo, MapInfo, Service } from '../../services/Service'; import { FormControl, TextField } from '@material-ui/core'; import { Alert, AlertTitle } from '@material-ui/lab'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import { - allMaps, - remove, - rename -} from '../../reducers/mapsListSlice' -import { Description } from '@material-ui/icons'; + activeInstance, +} from '../../reducers/serviceSlice' +import { useMutation, useQuery, useQueryClient } from 'react-query'; type DialogProps = { @@ -30,17 +28,32 @@ export type BasicMapInfo = { } function DeleteDialog(props: DialogProps) { - const dispatch = useDispatch() + const service: Service = useSelector(activeInstance); + const queryClient = useQueryClient(); const mapId = props.mapId; - const mapInfo: MapInfo | undefined = useSelector(allMaps). - find(m => m.id == mapId); + const mutation = useMutation((id: number) => service.deleteMap(id), + { + onSuccess: () => { + queryClient.invalidateQueries() + props.onClose(); + } + } + ); + + const { isLoading, error, data } = useQuery('maps', () => { + return service.fetchAllMaps(); + }); + + let mapInfo: MapInfo | undefined = undefined; + if (data) { + mapInfo = data.find((m) => m.id == mapId); + } const handleOnClose = (action: 'accept' | undefined): void => { if (action == 'accept' && mapInfo) { - dispatch(remove({ id: mapId })) + mutation.mutate(mapId); } - props.onClose(); }; return ( @@ -78,22 +91,21 @@ function RenameDialog(props: DialogProps) { const defaultModel: RenameModel = { name: '', description: '', id: -1 }; const [model, setModel] = React.useState(defaultModel); const [errorInfo, setErroInfo] = React.useState(); - const dispatch = useDispatch() const intl = useIntl(); - useEffect(() => { - const mapId: number = props.mapId; - if (mapId != -1) { - const mapInfo: MapInfo | undefined = useSelector(allMaps) - .find(m => m.id == props.mapId); + // useEffect(() => { + // const mapId: number = props.mapId; + // if (mapId != -1) { + // const mapInfo: MapInfo | undefined = useSelector(activeInstance) + // .find(m => m.id == props.mapId); - if (!mapInfo) { - throw "Please, reflesh the page."; - } + // if (!mapInfo) { + // throw "Please, reflesh the page."; + // } - setModel({ ...mapInfo }); - } - }, []); + // setModel({ ...mapInfo }); + // } + // }, []); const handleOnClose = (): void => { // Clean Up ... @@ -110,9 +122,9 @@ function RenameDialog(props: DialogProps) { // Fire rename ... const mapId: number = props.mapId; try { - dispatch(rename({ id: mapId, name: model.name, description: model.description })) + // dispatch(rename({ id: mapId, name: model.name, description: model.description })) handleOnClose(); - + } catch (errorInfo) { setErroInfo(errorInfo) } diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 02997df9..8a93a65d 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -24,7 +24,11 @@ import { CSSProperties } from 'react'; import MapActionMenu, { ActionType } from './MapActionMenu'; import ActionDialog, { DialogType } from './ActionDialog'; import { useSelector } from 'react-redux'; -import { allMaps, MapInfo } from '../../reducers/mapsListSlice'; +import { activeInstance } from '../../reducers/serviceSlice'; +import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; +import { ErrorInfo, MapInfo, Service } from '../../services/Service'; + + function descendingComparator(a: T, b: T, orderBy: keyof T) { if (b[orderBy] < a[orderBy]) { @@ -41,7 +45,7 @@ type Order = 'asc' | 'desc'; function getComparator( order: Order, orderBy: Key, -): (a: { [key in Key]: number | string | boolean | string[] }, b: { [key in Key]: number | string | string[] | boolean }) => number { +): (a: { [key in Key]: number | string | boolean | string[] | undefined }, b: { [key in Key]: number | string | string[] | boolean }) => number { return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); @@ -211,14 +215,22 @@ type ActionPanelState = { mapId: number } -function EnhancedTable() { +const EnhancedTable = () => { const classes = useStyles(); const [order, setOrder] = React.useState('asc'); const [orderBy, setOrderBy] = React.useState('modified'); const [selected, setSelected] = React.useState([]); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(5); - const mapsInfo: MapInfo[] = useSelector(allMaps); + const service: Service = useSelector(activeInstance); + + const { isLoading, error, data } = useQuery('maps', async () => { + + const result = await service.fetchAllMaps(); + return result; + }); + const mapsInfo: MapInfo[] = data ? data : []; + const [activeRowAction, setActiveRowAction] = React.useState(undefined); type ActiveDialog = { @@ -322,7 +334,7 @@ function EnhancedTable() { rowCount={mapsInfo.length} /> - {stableSort(mapsInfo, getComparator(order, orderBy)) + {isLoading ? () : stableSort(mapsInfo, getComparator(order, orderBy)) .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row: MapInfo) => { const isItemSelected = isSelected(row.id); @@ -391,11 +403,14 @@ function EnhancedTable() { {/* Action Dialog */} - setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1}/> + setActiveDialog(undefined)} mapId={activeDialog ? activeDialog.mapId : -1} /> ); } + + +const queryClient = new QueryClient(); const MapsPage = () => { useEffect(() => { @@ -403,20 +418,21 @@ const MapsPage = () => { }, []); return ( - - -

Header

-
- -

Nav

-
- - - -
+ + + +

Header

+
+ +

Nav

+
+ + + +
+
); } - export default MapsPage; diff --git a/packages/webapp/src/index.tsx b/packages/webapp/src/index.tsx index 54f15a9c..f95ac25c 100644 --- a/packages/webapp/src/index.tsx +++ b/packages/webapp/src/index.tsx @@ -1,17 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './app'; -import { BrowserRouter as Router } from 'react-router-dom'; -import axios from 'axios'; - async function bootstrapApplication() { ReactDOM.render( - - - , + , document.getElementById('root') as HTMLElement ) } bootstrapApplication() + \ No newline at end of file diff --git a/packages/webapp/src/reducers/mapsListSlice.ts b/packages/webapp/src/reducers/mapsListSlice.ts deleted file mode 100644 index 0d8d877c..00000000 --- a/packages/webapp/src/reducers/mapsListSlice.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import axios from 'axios'; -import { RestService, Service } from '../services/Service'; - -function createMapInfo( - id: number, - starred: boolean, - name: string, - labels: [string], - creator: string, - modified: number, - description: string -): MapInfo { - return { id, name, labels, creator, modified, starred, description }; -} - -const maps = [ - createMapInfo(1, true, "El Mapa", [""], "Paulo", 67, ""), - createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67, ""), - createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67, "") -]; - -export type MapInfo = { - id: number; - starred: boolean; - name: string; - labels: [string]; - creator: string; - modified: number; - description: string -} - -interface MapsListState { - maps: MapInfo[] -} - -type RutimeConfig = { - apiBaseUrl: string; -} - -async function loadRuntimeConfig() { - let result: RutimeConfig | undefined; - - await axios.get("runtime-config.json" - ).then(response => { - // All was ok, let's sent to success page ... - result = response.data as RutimeConfig; - console.log("Dynamic configuration->" + response.data); - }).catch(e => { - console.log(e) - }); - - if (!result) { - // Ok, try to create a default configuration relative to the current path ... - console.log("Configuration could not be loaded, falback to default config.") - const location = window.location; - const basePath = location.protocol + "//" + location.host + "/" + location.pathname.split('/')[1] - - result = { - apiBaseUrl: basePath - } - } - return result; -} - -const initialState: MapsListState = { maps: maps }; - -const service: Service = new RestService("", () => { console.log("401 error") }); - -type RemovePayload = { - id: number; -} - -type RenamePayload = { - id: number; - name: string; - description: string | undefined; - -} - -export const mapsListSlice = createSlice({ - name: 'maps', - initialState: initialState, - reducers: { - remove(state, action: PayloadAction) { - const maps: MapInfo[] = state.maps as MapInfo[]; - const payload = action.payload; - state.maps = maps.filter(map => map.id != payload.id); - }, - rename(state, action: PayloadAction) { - let maps: MapInfo[] = state.maps as MapInfo[]; - const payload = action.payload; - - const mapInfo = maps.find(m => m.id == payload.id); - if (mapInfo) { - mapInfo.name = payload.name; - mapInfo.description = payload.description ? payload.description: ""; - - // Remove and add the new map. - maps = maps.filter(map => map.id != payload.id); - maps.push(mapInfo); - - state.maps = maps; - - } - } - }, -}); - -export const allMaps = (state: any): MapInfo[] => state.mapsList.maps; - -export const { remove, rename } = mapsListSlice.actions -export default mapsListSlice.reducer \ No newline at end of file diff --git a/packages/webapp/src/reducers/serviceSlice.ts b/packages/webapp/src/reducers/serviceSlice.ts new file mode 100644 index 00000000..c0eb9b5b --- /dev/null +++ b/packages/webapp/src/reducers/serviceSlice.ts @@ -0,0 +1,58 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import axios from 'axios'; +import { ErrorInfo } from 'react'; +import { RestService, Service } from '../services/Service'; + +type RutimeConfig = { + apiBaseUrl: string; +} + +async function loadRuntimeConfig() { + let result: RutimeConfig | undefined; + + await axios.get("runtime-config.json" + ).then(response => { + // All was ok, let's sent to success page ... + result = response.data as RutimeConfig; + console.log("Dynamic configuration->" + response.data); + }).catch(e => { + console.log(e) + }); + + if (!result) { + // Ok, try to create a default configuration relative to the current path ... + console.log("Configuration could not be loaded, falback to default config.") + const location = window.location; + const basePath = location.protocol + "//" + location.host + "/" + location.pathname.split('/')[1] + + result = { + apiBaseUrl: basePath + } + } + return result; +} + + +interface ServiceState { + instance: Service +} + +const initialState: ServiceState = { + instance: new RestService("", () => { console.log("401 error") }) +}; + +export const serviceSlice = createSlice({ + name: "service", + initialState: initialState, + reducers: { + initialize(state, action: PayloadAction) { + state.instance = new RestService("", () => { console.log("401 error") }); + } + }, +}); + +export const activeInstance = (state: any): Service => { + return state.service.instance; +} +export default serviceSlice.reducer + diff --git a/packages/webapp/src/services/Service.ts b/packages/webapp/src/services/Service.ts index 7a11f1a1..573beb86 100644 --- a/packages/webapp/src/services/Service.ts +++ b/packages/webapp/src/services/Service.ts @@ -1,4 +1,3 @@ -import { Description } from '@material-ui/icons' import axios from 'axios' export type NewUser = { @@ -47,11 +46,31 @@ interface Service { class RestService implements Service { private baseUrl: string; private authFailed: () => void + private maps: MapInfo[] = []; constructor(baseUrl: string, authFailed: () => void) { this.baseUrl = baseUrl; + + // Remove, just for develop .... + function createMapInfo( + id: number, + starred: boolean, + name: string, + labels: [string], + creator: string, + modified: number, + description: string + ): MapInfo { + return { id, name, labels, creator, modified, starred, description }; + } + this.maps = [ + createMapInfo(1, true, "El Mapa", [""], "Paulo", 67, ""), + createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67, ""), + createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67, "") + ]; } + loadMapInfo(id: number): Promise { return Promise.resolve({ name: 'My Map', description: 'My Description' }); } @@ -66,7 +85,8 @@ class RestService implements Service { }); } - async deleteMap(id: number): Promise { + deleteMap(id: number): Promise { + this.maps = this.maps.filter(m => m.id != id); return Promise.resolve(); } @@ -87,27 +107,8 @@ class RestService implements Service { return new Promise(handler); } - async fetchAllMaps(): Promise { - - function createMapInfo( - id: number, - starred: boolean, - name: string, - labels: [string], - creator: string, - modified: number, - description: string - ): MapInfo { - return { id, name, labels, creator, modified, starred, description}; - } - - const maps = [ - createMapInfo(1, true, "El Mapa", [""], "Paulo", 67,""), - createMapInfo(2, false, "El Mapa2", [""], "Paulo2", 67,""), - createMapInfo(3, false, "El Mapa3", [""], "Paulo3", 67,"") - ]; - - return Promise.resolve(maps); + fetchAllMaps(): Promise { + return Promise.resolve(this.maps); } resetPassword(email: string): Promise { diff --git a/packages/webapp/src/store.ts b/packages/webapp/src/store.ts index 65ce9614..a9afbf8d 100644 --- a/packages/webapp/src/store.ts +++ b/packages/webapp/src/store.ts @@ -1,12 +1,12 @@ -import { configureStore } from '@reduxjs/toolkit'; -import mapsListReducer from './reducers/mapsListSlice'; +import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; +import serviceReducer from './reducers/serviceSlice'; - // Create Service object... - const store = configureStore({ - reducer: { - mapsList: mapsListReducer - } - }); +// Create Service object... +const store = configureStore({ + reducer: { + service: serviceReducer + } +}); - export default store; \ No newline at end of file +export default store; \ No newline at end of file diff --git a/packages/webapp/src/tsconfig.json b/packages/webapp/src/tsconfig.json index ad7c4282..5609ad7e 100644 --- a/packages/webapp/src/tsconfig.json +++ b/packages/webapp/src/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, - "noImplicitAny": true, + "noImplicitAny": false, "module": "commonjs", "target": "es5", "jsx": "react", diff --git a/packages/webapp/tsconfig.json b/packages/webapp/tsconfig.json index e1cb573a..0dd8ee40 100644 --- a/packages/webapp/tsconfig.json +++ b/packages/webapp/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, - "noImplicitAny": true, + "noImplicitAny": false, "module": "commonjs", "target": "es5", "jsx": "react",