vega-frontend-monorepo/libs/react-helpers/src/hooks/use-fetch.ts
Sam Keen e0bcfe7bbe
Feat/231-explorer-infinite-loading-blocks (#306)
* Making a start with react-window-infinite-loader for the blocks infinite scrolling

* WIP block explorer infinte scroll

* WIP pairing

* pairing tidying

* Applied refetch url params more cleanly, moved useFetch to react-helpers lib

* Add notice of new blocks created since page load, some cleanup of blocks-infinite-list.tsx component

* Attempting a refresh of the 'new blocks' value in blocks-refetch.tsx

* Correctly updating state based on previous value

* Update libs/react-helpers/src/hooks/use-fetch.ts

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>

* Update libs/react-helpers/src/hooks/use-fetch.ts

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>

* Update apps/explorer/src/app/components/blocks/blocks-refetch.tsx

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>

* Cleanup from convos and PR

* Prettier formatting

* struggling with websocket tests

* struggling with websocket tests

* Progress on websocket tests for blocks-refetch.tsx

* Tests for blocks-refetch

* Tests for blocks-infinite-list

* Scroll test for blocks-infinite-list

* Defined NOOP in blocks-infinite-list.tsx

* Separate web sockets for each test

* Separate web sockets for each test

* Tweaked e2e tests to account for blocks taking longer to load

* fix tests

* Removed nx knowledge of empty simple-trading-app-e2e for now

* mock at use fetch level instead of at fetch level

* fix edge cases and add further tests

* fix failing e2e tests

* rename test

* Update apps/explorer-e2e/src/support/pages/blocks-page.ts

* Update apps/explorer-e2e/src/support/pages/blocks-page.ts

* rename

* test: use explicit wait for rather than times

* style: remove console

* test: correct env file

* Revert "test: correct env file"

This reverts commit d01d3cfa5e.

* think env var is incorrect

* correct env file

* fix flakiness

* add minor wait for test flakiness

* longer timeout

Co-authored-by: Dexter <dexter.edwards93@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2022-05-13 12:03:08 +01:00

118 lines
3.0 KiB
TypeScript

import { useCallback, useEffect, useReducer, useRef } from 'react';
interface State<T> {
data?: T;
error?: Error;
loading?: boolean;
}
enum ActionType {
LOADING = 'LOADING',
ERROR = 'ERROR',
FETCHED = 'FETCHED',
}
// discriminated union type
type Action<T> =
| { type: ActionType.LOADING }
| { type: ActionType.FETCHED; payload: T }
| { type: ActionType.ERROR; error: Error };
export const useFetch = <T>(
url: string,
options?: RequestInit,
initialFetch = true
): {
state: State<T>;
refetch: (
params?: Record<string, string | number | null | undefined> | undefined
) => Promise<T | undefined>;
} => {
// Used to prevent state update if the component is unmounted
const cancelRequest = useRef<boolean>(false);
const initialState: State<T> = {
error: undefined,
data: undefined,
loading: false,
};
// Keep state logic separated
const fetchReducer = (state: State<T>, action: Action<T>): State<T> => {
switch (action.type) {
case ActionType.LOADING:
return { ...initialState, loading: true };
case ActionType.FETCHED:
return { ...initialState, data: action.payload, loading: false };
case ActionType.ERROR:
return { ...initialState, error: action.error, loading: false };
}
};
const [state, dispatch] = useReducer(fetchReducer, initialState);
const fetchCallback = useCallback(
async (params?: Record<string, string | null | undefined | number>) => {
if (!url) return;
const fetchData = async () => {
dispatch({ type: ActionType.LOADING });
let data;
try {
const assembledUrl = new URL(url);
if (params) {
for (const [key, value] of Object.entries(params)) {
if (value) {
assembledUrl.searchParams.set(key, value.toString());
}
}
}
const response = await fetch(assembledUrl.toString(), options);
if (!response.ok) {
throw new Error(response.statusText);
}
data = (await response.json()) as T;
if (data && 'error' in data) {
// @ts-ignore - data.error
throw new Error(data.error);
}
if (cancelRequest.current) return;
dispatch({ type: ActionType.FETCHED, payload: data });
} catch (error) {
if (cancelRequest.current) return;
dispatch({ type: ActionType.ERROR, error: error as Error });
}
return data;
};
return fetchData();
},
// Do nothing if the url is not given
// eslint-disable-next-line react-hooks/exhaustive-deps
[url]
);
useEffect(() => {
cancelRequest.current = false;
if (initialFetch) {
fetchCallback();
}
}, [fetchCallback, initialFetch, url]);
useEffect(() => {
// Use the cleanup function for avoiding a possibly...
// ...state update after the component was unmounted
return () => {
cancelRequest.current = true;
};
}, []);
return {
state,
refetch: fetchCallback,
};
};