e0bcfe7bbe
* 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>
118 lines
3.0 KiB
TypeScript
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,
|
|
};
|
|
};
|