Skip to content
This repository was archived by the owner on Sep 29, 2020. It is now read-only.

Commit 0f6c398

Browse files
authored
TFP-3523 Skriv om henting av data til visittkort for å unngå race con… (#1217)
* TFP-3523 Skriv om henting av data til visittkort for å unngå race condition (Ta i bruk hooks)
1 parent 40a317a commit 0f6c398

File tree

68 files changed

+2493
-98
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2493
-98
lines changed

packages/rest-api-hooks/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export { default as RestApiState } from './src/RestApiState';
2+
3+
export { default as getUseRestApi } from './src/local-data/useRestApi';
4+
export { default as useRestApiRunner } from './src/local-data/useRestApiRunner';
5+
6+
export { RestApiStateContext, RestApiProvider } from './src/RestApiContext';
7+
export { default as useGlobalStateRestApi } from './src/global-data/useGlobalStateRestApi';
8+
export { default as useGlobalStateRestApiData } from './src/global-data/useGlobalStateRestApiData';
9+
10+
export { RestApiErrorProvider } from './src/error/RestApiErrorContext';
11+
export { default as useRestApiError } from './src/error/useRestApiError';
12+
export { default as useRestApiErrorDispatcher } from './src/error/useRestApiErrorDispatcher';

packages/rest-api-hooks/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@fpsak-frontend/rest-api-hooks",
3+
"version": "1.0.0",
4+
"module": "index.ts",
5+
"license": "MIT",
6+
"private": true,
7+
"dependencies": {
8+
"moment": "^2.27.0",
9+
"react": "^16.13.1",
10+
"react-redux": "^7.2.0",
11+
"redux": "^4.0.5",
12+
"redux-thunk": "^2.3.0",
13+
"reselect": "^4.0.0",
14+
"@fpsak-frontend/rest-api": "1.0.0"
15+
},
16+
"devDependencies": {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, {
2+
createContext, useReducer, FunctionComponent, ReactNode,
3+
} from 'react';
4+
5+
const defaultInitialState = {};
6+
7+
type Action = {type: 'success', key: string, data: any } | {type: 'remove', key: string}
8+
type Dispatch = (action: Action) => void
9+
type State = {[key: string]: any};
10+
11+
export const RestApiStateContext = createContext<State>(defaultInitialState);
12+
export const RestApiDispatchContext = createContext<Dispatch | undefined>(undefined);
13+
14+
interface OwnProps {
15+
children: ReactNode;
16+
requestApi: any;
17+
initialState?: {[key in string]: any};
18+
}
19+
20+
/**
21+
* Håndterer state for data som skal hentes fra backend kun en gang og som en trenger aksess til
22+
* mange steder i applikasjonen.
23+
*
24+
* Tilbyr i tillegg et requestApi for hooks som henter data fra backend
25+
*/
26+
export const RestApiProvider: FunctionComponent<OwnProps> = ({
27+
children,
28+
initialState,
29+
}): JSX.Element => {
30+
const [state, dispatch] = useReducer((oldState, action) => {
31+
switch (action.type) {
32+
case 'success':
33+
return {
34+
...oldState,
35+
[action.key]: action.data,
36+
};
37+
case 'remove':
38+
return Object.keys(oldState).filter((key) => key !== action.key).reduce((acc, key) => ({
39+
...acc,
40+
[key]: oldState[key],
41+
}), {});
42+
default:
43+
throw new Error();
44+
}
45+
}, initialState || defaultInitialState);
46+
47+
return (
48+
<RestApiStateContext.Provider value={state}>
49+
<RestApiDispatchContext.Provider value={dispatch}>
50+
{children}
51+
</RestApiDispatchContext.Provider>
52+
</RestApiStateContext.Provider>
53+
);
54+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
enum RestApiState {
2+
NOT_STARTED = 'NOT_STARTED',
3+
LOADING = 'LOADING',
4+
SUCCESS = 'SUCCESS',
5+
ERROR = 'ERROR',
6+
}
7+
8+
export default RestApiState;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, {
2+
createContext, useReducer, FunctionComponent, ReactNode,
3+
} from 'react';
4+
5+
const defaultInitialState = {
6+
errors: [],
7+
};
8+
9+
type Action = {type: 'add', data: any } | {type: 'remove'}
10+
type Dispatch = (action: Action) => void
11+
type State = {errors: any[]}
12+
13+
export const RestApiErrorStateContext = createContext<State>(defaultInitialState);
14+
export const RestApiErrorDispatchContext = createContext<Dispatch | undefined>(undefined);
15+
16+
interface OwnProps {
17+
children: ReactNode;
18+
initialState?: State;
19+
}
20+
21+
/**
22+
* State for å lagre feil oppstår ved rest-kall
23+
*/
24+
export const RestApiErrorProvider: FunctionComponent<OwnProps> = ({
25+
children,
26+
initialState,
27+
}): JSX.Element => {
28+
const [state, dispatch] = useReducer((oldState, action) => {
29+
switch (action.type) {
30+
case 'add':
31+
return {
32+
errors: oldState.errors.concat(action.data),
33+
};
34+
case 'remove':
35+
return defaultInitialState;
36+
default:
37+
throw new Error();
38+
}
39+
}, initialState || defaultInitialState);
40+
41+
return (
42+
<RestApiErrorStateContext.Provider value={state}>
43+
<RestApiErrorDispatchContext.Provider value={dispatch}>
44+
{children}
45+
</RestApiErrorDispatchContext.Provider>
46+
</RestApiErrorStateContext.Provider>
47+
);
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useContext } from 'react';
2+
3+
import { RestApiErrorStateContext } from './RestApiErrorContext';
4+
5+
/**
6+
* Hook for å hente alle feil fra rest-kall
7+
*/
8+
const useRestApiError = () => {
9+
const state = useContext(RestApiErrorStateContext);
10+
return state.errors;
11+
};
12+
13+
export default useRestApiError;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useContext, useCallback } from 'react';
2+
3+
import { RestApiErrorDispatchContext } from './RestApiErrorContext';
4+
5+
/**
6+
* Hook for å legge til eller fjerne feil fra rest-kall
7+
*/
8+
const useRestApiErrorDispatcher = () => {
9+
const dispatch = useContext(RestApiErrorDispatchContext);
10+
11+
const addErrorMessage = useCallback((data) => dispatch({ type: 'add', data }), []);
12+
const removeErrorMessages = useCallback(() => dispatch({ type: 'remove' }), []);
13+
14+
return {
15+
addErrorMessage,
16+
removeErrorMessages,
17+
};
18+
};
19+
20+
export default useRestApiErrorDispatcher;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useState, useEffect, useContext } from 'react';
2+
3+
import { NotificationMapper, RequestApi } from '@fpsak-frontend/rest-api-new';
4+
5+
import useRestApiErrorDispatcher from '../error/useRestApiErrorDispatcher';
6+
import { RestApiDispatchContext } from '../RestApiContext';
7+
import RestApiState from '../RestApiState';
8+
9+
interface RestApiData<T> {
10+
state: RestApiState;
11+
error?: Error;
12+
data?: T;
13+
}
14+
15+
/**
16+
* Hook som henter data fra backend (ved mount) og deretter lagrer i @see RestApiContext
17+
*/
18+
const getUseGlobalStateRestApi = (requestApi: RequestApi) => function useGlobalStateRestApi<T>(key: string, params: any = {}):RestApiData<T> {
19+
const [data, setData] = useState({
20+
state: RestApiState.LOADING,
21+
error: undefined,
22+
data: undefined,
23+
});
24+
25+
const { addErrorMessage } = useRestApiErrorDispatcher();
26+
const notif = new NotificationMapper();
27+
notif.addRequestErrorEventHandlers((errorData, type) => {
28+
addErrorMessage({ ...errorData, type });
29+
});
30+
31+
const dispatch = useContext(RestApiDispatchContext);
32+
33+
useEffect(() => {
34+
dispatch({ type: 'remove', key });
35+
36+
requestApi.startRequest(key, params, notif)
37+
.then((dataRes) => {
38+
dispatch({ type: 'success', key, data: dataRes.payload });
39+
setData({
40+
state: RestApiState.SUCCESS,
41+
data: dataRes.payload,
42+
error: undefined,
43+
});
44+
})
45+
.catch((error) => {
46+
setData({
47+
state: RestApiState.ERROR,
48+
data: undefined,
49+
error,
50+
});
51+
});
52+
}, []);
53+
54+
return data;
55+
};
56+
57+
export default getUseGlobalStateRestApi;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useContext } from 'react';
2+
3+
import { RestApiStateContext } from '../RestApiContext';
4+
5+
/**
6+
* Hook som bruker respons som allerede er hentet fra backend. For å kunne bruke denne
7+
* må @see useGlobalStateRestApi først brukes for å hente data fra backend
8+
*/
9+
function useGlobalStateRestApiData<T>(key: string): T {
10+
const state = useContext(RestApiStateContext);
11+
return state[key];
12+
}
13+
14+
export default useGlobalStateRestApiData;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
useState, useEffect, DependencyList,
3+
} from 'react';
4+
5+
import { REQUEST_POLLING_CANCELLED, NotificationMapper, RequestApi } from '@fpsak-frontend/rest-api-new';
6+
7+
import useRestApiErrorDispatcher from '../error/useRestApiErrorDispatcher';
8+
import RestApiState from '../RestApiState';
9+
10+
interface RestApiData<T> {
11+
state: RestApiState;
12+
error?: Error;
13+
data?: T;
14+
}
15+
16+
interface Options {
17+
updateTriggers?: DependencyList;
18+
keepData?: boolean;
19+
suspendRequest?: boolean;
20+
}
21+
22+
const defaultOptions = {
23+
updateTriggers: [],
24+
keepData: false,
25+
suspendRequest: false,
26+
};
27+
28+
/**
29+
* Hook som utfører et restkall ved mount. En kan i tillegg legge ved en dependencies-liste som kan trigge ny henting når data
30+
* blir oppdatert. Hook returnerer rest-kallets status/resultat/feil
31+
*/
32+
const getUseRestApi = (requestApi: RequestApi) => function useRestApi<T>(key: string, params: any = {}, options: Options = defaultOptions):RestApiData<T> {
33+
const [data, setData] = useState({
34+
state: RestApiState.NOT_STARTED,
35+
error: undefined,
36+
data: undefined,
37+
});
38+
39+
const { addErrorMessage } = useRestApiErrorDispatcher();
40+
const notif = new NotificationMapper();
41+
notif.addRequestErrorEventHandlers((errorData, type) => {
42+
addErrorMessage({ ...errorData, type });
43+
});
44+
45+
useEffect(() => {
46+
if (requestApi.hasPath(key) && !options.suspendRequest) {
47+
setData((oldState) => ({
48+
state: RestApiState.LOADING,
49+
error: undefined,
50+
data: options.keepData ? oldState.data : undefined,
51+
}));
52+
53+
requestApi.startRequest(key, params, notif)
54+
.then((dataRes) => {
55+
if (dataRes.payload !== REQUEST_POLLING_CANCELLED) {
56+
setData({
57+
state: RestApiState.SUCCESS,
58+
data: dataRes.payload,
59+
error: undefined,
60+
});
61+
}
62+
})
63+
.catch((error) => {
64+
setData({
65+
state: RestApiState.ERROR,
66+
data: undefined,
67+
error,
68+
});
69+
});
70+
} else {
71+
setData({
72+
state: RestApiState.NOT_STARTED,
73+
error: undefined,
74+
data: undefined,
75+
});
76+
}
77+
}, options.updateTriggers);
78+
79+
return data;
80+
};
81+
82+
export default getUseRestApi;

0 commit comments

Comments
 (0)