mirror of
https://github.com/apache/superset.git
synced 2024-09-06 22:07:34 -04:00
implement state management in redux
This commit is contained in:
parent
2c6fd0abb8
commit
d64d50a99f
@ -17,8 +17,10 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { Action, Dispatch } from 'redux';
|
||||||
import { makeApi } from '@superset-ui/core';
|
import { makeApi } from '@superset-ui/core';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
export enum ResourceStatus {
|
export enum ResourceStatus {
|
||||||
LOADING = 'loading',
|
LOADING = 'loading',
|
||||||
@ -32,6 +34,10 @@ export enum ResourceStatus {
|
|||||||
*/
|
*/
|
||||||
export type Resource<T> = LoadingState | CompleteState<T> | ErrorState;
|
export type Resource<T> = LoadingState | CompleteState<T> | ErrorState;
|
||||||
|
|
||||||
|
export type ReduxState = {
|
||||||
|
[url: string]: Resource<any>;
|
||||||
|
};
|
||||||
|
|
||||||
// Trying out something a little different: a separate type per status.
|
// Trying out something a little different: a separate type per status.
|
||||||
// This should let Typescript know whether a Resource has a result or error.
|
// This should let Typescript know whether a Resource has a result or error.
|
||||||
// It's possible that I'm expecting too much from Typescript here.
|
// It's possible that I'm expecting too much from Typescript here.
|
||||||
@ -61,12 +67,87 @@ type ErrorState = {
|
|||||||
error: Error;
|
error: Error;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: LoadingState = {
|
const loadingState: LoadingState = {
|
||||||
status: ResourceStatus.LOADING,
|
status: ResourceStatus.LOADING,
|
||||||
result: null,
|
result: null,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RESOURCE_FETCH_START = 'RESOURCE_FETCH_START';
|
||||||
|
const RESOURCE_FETCH_COMPLETE = 'RESOURCE_FETCH_COMPLETE';
|
||||||
|
const RESOURCE_FETCH_ERROR = 'RESROUCE_FETCH_ERROR';
|
||||||
|
|
||||||
|
type FetchStart = {
|
||||||
|
type: typeof RESOURCE_FETCH_START;
|
||||||
|
endpoint: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FetchComplete<T = any> = {
|
||||||
|
type: typeof RESOURCE_FETCH_COMPLETE;
|
||||||
|
endpoint: string;
|
||||||
|
result: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FetchError = {
|
||||||
|
type: typeof RESOURCE_FETCH_ERROR;
|
||||||
|
endpoint: string;
|
||||||
|
error: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResourceAction = FetchStart | FetchComplete | FetchError;
|
||||||
|
|
||||||
|
export type ResourcesState = Record<string, Resource<any>>;
|
||||||
|
|
||||||
|
export const initialResourcesState: ResourcesState = {};
|
||||||
|
|
||||||
|
export function resourcesReducer(
|
||||||
|
state: ResourcesState = initialResourcesState,
|
||||||
|
action: ResourceAction,
|
||||||
|
): ResourcesState {
|
||||||
|
switch (action.type) {
|
||||||
|
case RESOURCE_FETCH_START: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[action.endpoint]: {
|
||||||
|
status: ResourceStatus.LOADING,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case RESOURCE_FETCH_COMPLETE: {
|
||||||
|
const { endpoint, result } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[endpoint]: {
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case RESOURCE_FETCH_ERROR: {
|
||||||
|
const { endpoint, error } = action as FetchError;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[endpoint]: {
|
||||||
|
status: ResourceStatus.ERROR,
|
||||||
|
result: null,
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReduxRootState = { apiResources: ResourcesState };
|
||||||
|
|
||||||
|
const selectResourceForEndpoint = <RESULT>(endpoint: string) => (
|
||||||
|
state: ReduxRootState,
|
||||||
|
): Resource<RESULT> | null => state.apiResources[endpoint] ?? null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A general-purpose hook to fetch the response from an endpoint.
|
* A general-purpose hook to fetch the response from an endpoint.
|
||||||
* Returns the full response body from the API, including metadata.
|
* Returns the full response body from the API, including metadata.
|
||||||
@ -87,21 +168,19 @@ const initialState: LoadingState = {
|
|||||||
export function useApiResourceFullBody<RESULT>(
|
export function useApiResourceFullBody<RESULT>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
): Resource<RESULT> {
|
): Resource<RESULT> {
|
||||||
const [resource, setResource] = useState<Resource<RESULT>>(initialState);
|
const dispatch = useDispatch<Dispatch<ResourceAction>>();
|
||||||
const cancelRef = useRef<() => void>(() => {});
|
const resource = useSelector(selectResourceForEndpoint<RESULT>(endpoint));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If refresh is implemented, this will need to change.
|
if (resource != null) {
|
||||||
// The previous values should stay during refresh.
|
// fetching already underway/complete, don't duplicate work.
|
||||||
setResource(initialState);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// when this effect runs, the endpoint has changed.
|
dispatch({
|
||||||
// cancel any current calls so that state doesn't get messed up.
|
type: RESOURCE_FETCH_START,
|
||||||
cancelRef.current();
|
endpoint,
|
||||||
let cancelled = false;
|
});
|
||||||
cancelRef.current = () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchResource = makeApi<{}, RESULT>({
|
const fetchResource = makeApi<{}, RESULT>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -110,31 +189,22 @@ export function useApiResourceFullBody<RESULT>(
|
|||||||
|
|
||||||
fetchResource({})
|
fetchResource({})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!cancelled) {
|
dispatch({
|
||||||
setResource({
|
type: RESOURCE_FETCH_COMPLETE,
|
||||||
status: ResourceStatus.COMPLETE,
|
endpoint,
|
||||||
result,
|
result,
|
||||||
error: null,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (!cancelled) {
|
dispatch({
|
||||||
setResource({
|
type: RESOURCE_FETCH_ERROR,
|
||||||
status: ResourceStatus.ERROR,
|
endpoint,
|
||||||
result: null,
|
error,
|
||||||
error,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}, [endpoint, resource, dispatch]);
|
||||||
|
|
||||||
// Cancel the request when the component un-mounts
|
return resource ?? loadingState;
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, [endpoint]);
|
|
||||||
|
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,11 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {
|
export * from './apiResources';
|
||||||
useApiResourceFullBody,
|
|
||||||
useApiV1Resource,
|
|
||||||
useTransformedResource,
|
|
||||||
} from './apiResources';
|
|
||||||
|
|
||||||
// A central catalog of API Resource hooks.
|
// A central catalog of API Resource hooks.
|
||||||
// Add new API hooks here, organized under
|
// Add new API hooks here, organized under
|
||||||
|
@ -47,6 +47,7 @@ import getFilterConfigsFromFormdata from '../util/getFilterConfigsFromFormdata';
|
|||||||
import getLocationHash from '../util/getLocationHash';
|
import getLocationHash from '../util/getLocationHash';
|
||||||
import newComponentFactory from '../util/newComponentFactory';
|
import newComponentFactory from '../util/newComponentFactory';
|
||||||
import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox';
|
import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox';
|
||||||
|
import { initialResourcesState } from 'src/common/hooks/apiResources/apiResources';
|
||||||
|
|
||||||
export default function getInitialState(bootstrapData) {
|
export default function getInitialState(bootstrapData) {
|
||||||
const { user_id, datasources, common, editMode, urlParams } = bootstrapData;
|
const { user_id, datasources, common, editMode, urlParams } = bootstrapData;
|
||||||
@ -308,5 +309,6 @@ export default function getInitialState(bootstrapData) {
|
|||||||
dashboardLayout,
|
dashboardLayout,
|
||||||
messageToasts: [],
|
messageToasts: [],
|
||||||
impressionId: shortid.generate(),
|
impressionId: shortid.generate(),
|
||||||
|
apiResources: initialResourcesState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
import { resourcesReducer } from 'src/common/hooks/apiResources';
|
||||||
import charts from '../../chart/chartReducer';
|
import charts from '../../chart/chartReducer';
|
||||||
import dashboardInfo from './dashboardInfo';
|
import dashboardInfo from './dashboardInfo';
|
||||||
import dashboardState from './dashboardState';
|
import dashboardState from './dashboardState';
|
||||||
@ -41,4 +42,5 @@ export default combineReducers({
|
|||||||
impressionId,
|
impressionId,
|
||||||
messageToasts,
|
messageToasts,
|
||||||
sliceEntities,
|
sliceEntities,
|
||||||
|
apiResources: resourcesReducer,
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user