Skip to content

Commit cc329b9

Browse files
committed
fix: πŸ› Rerendering with initial fetching state
βœ… Closes: #94
1 parent 13e90d6 commit cc329b9

File tree

5 files changed

+119
-16
lines changed

5 files changed

+119
-16
lines changed

β€Žpackages/react/__tests__/features/use-fetch/use-fetch.rerender.spec.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1+
import { useRef } from "react";
12
import { useDidMount, useDidUpdate } from "@better-hooks/lifecycle";
23
import { render } from "@testing-library/react";
34

45
import { useFetch } from "hooks/use-fetch";
56
import { startServer, resetInterceptors, stopServer, createRequestInterceptor } from "../../server";
67
import { client, createRequest, sleep, waitForRender } from "../../utils";
78

9+
const useWillMount = (fn: () => void) => {
10+
const mounted = useRef(false);
11+
12+
if (!mounted.current) {
13+
mounted.current = true;
14+
fn();
15+
}
16+
};
17+
818
describe("useFetch [ Rerender ]", () => {
919
let rerenders = 0;
1020
let request = createRequest();
@@ -38,6 +48,31 @@ describe("useFetch [ Rerender ]", () => {
3848
createRequestInterceptor(request, { delay: fetchTime });
3949
});
4050

51+
// FIXES https://github.com/BetterTyped/hyper-fetch/issues/94
52+
it("should start in loading state", async () => {
53+
let isLoading = false;
54+
55+
function Page() {
56+
const { loading } = useFetch(request);
57+
58+
useWillMount(() => {
59+
isLoading = loading;
60+
});
61+
62+
rerenders += 1;
63+
64+
return <div>{loading ? "loading" : "error"}</div>;
65+
}
66+
67+
render(<Page />);
68+
69+
await waitForDoubleFetch();
70+
await sleep(200);
71+
72+
expect(isLoading).toBe(true);
73+
expect(rerenders).toBe(2);
74+
});
75+
4176
it("should not rerender when the same data is received from backend", async () => {
4277
function Page() {
4378
const { data } = useFetch(request, { refresh: true, refreshTime: 10 });
@@ -88,7 +123,7 @@ describe("useFetch [ Rerender ]", () => {
88123

89124
await waitForDoubleFetch();
90125

91-
expect(rerenders).toBe(3);
126+
expect(rerenders).toBe(2);
92127
});
93128
it("should rerender while using the changed key", async () => {
94129
function Page() {

β€Žpackages/react/src/helpers/use-tracked-state/use-tracked-state.hooks.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ import {
1616
UseTrackedStateProps,
1717
UseTrackedStateReturn,
1818
} from "./use-tracked-state.types";
19-
import { getDetailsState, getInitialState, getValidCacheData, isStaleCacheData } from "./use-tracked-state.utils";
19+
import {
20+
getDetailsState,
21+
getInitialState,
22+
getIsInitiallyLoading,
23+
getValidCacheData,
24+
isStaleCacheData,
25+
} from "./use-tracked-state.utils";
2026

2127
/**
2228
*
@@ -33,13 +39,23 @@ export const useTrackedState = <T extends RequestInstance>({
3339
deepCompare,
3440
dependencyTracking,
3541
defaultCacheEmitting = true,
42+
/**
43+
* useFetch only
44+
*/
45+
disabled,
46+
/**
47+
* useFetch only
48+
*/
49+
revalidate,
3650
}: UseTrackedStateProps<T>): UseTrackedStateReturn<T> => {
3751
const { client, cacheKey, queueKey, cacheTime, responseMapper } = request;
3852
const { cache, requestManager } = client;
3953

4054
const forceUpdate = useForceUpdate();
4155

42-
const state = useRef<UseTrackedStateType<T>>(getInitialState(initialData, dispatcher, request));
56+
const state = useRef<UseTrackedStateType<T>>(
57+
getInitialState({ initialResponse: initialData, dispatcher, request, disabled }),
58+
);
4359
const renderKeys = useRef<Array<keyof UseTrackedStateType<T>>>([]);
4460
const isProcessingData = useRef("");
4561

@@ -84,6 +100,15 @@ export const useTrackedState = <T extends RequestInstance>({
84100
>(cacheKey);
85101
const cacheState = getValidCacheData<T>(request, initialData, cacheData);
86102

103+
// Handle initial loading state
104+
state.current.loading = getIsInitiallyLoading({
105+
queryKey: request.queueKey,
106+
dispatcher,
107+
disabled,
108+
revalidate,
109+
hasState: !!cacheState,
110+
});
111+
87112
const hasInitialState = isEqual(initialData?.data, state.current.data);
88113
const hasState = !!(state.current.data || state.current.error) && !hasInitialState;
89114
const shouldLoadInitialCache = !hasState && !!state.current.data;
@@ -232,7 +257,7 @@ export const useTrackedState = <T extends RequestInstance>({
232257
isRetry: false,
233258
isOffline: false,
234259
});
235-
} else {
260+
} else if (loading !== state.current.loading) {
236261
state.current.loading = loading;
237262
renderKeyTrigger(["loading"]);
238263
}

β€Žpackages/react/src/helpers/use-tracked-state/use-tracked-state.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export type UseTrackedStateProps<T extends RequestInstance> = {
2222
dependencyTracking: boolean;
2323
defaultCacheEmitting?: boolean;
2424
deepCompare: boolean | typeof isEqual;
25+
disabled?: boolean;
26+
revalidate?: boolean;
2527
};
2628

2729
export type UseTrackedStateReturn<T extends RequestInstance> = [

β€Žpackages/react/src/helpers/use-tracked-state/use-tracked-state.utils.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,48 @@ export const getTimestamp = (timestamp?: NullableType<number | Date>) => {
6464
return timestamp ? new Date(timestamp) : null;
6565
};
6666

67-
export const getInitialState = <T extends RequestInstance>(
68-
initialData: NullableType<Partial<ExtractAdapterReturnType<T>>>,
69-
dispatcher: Dispatcher,
70-
request: T,
71-
): UseTrackedStateType<T> => {
67+
export const getIsInitiallyLoading = ({
68+
queryKey,
69+
dispatcher,
70+
hasState,
71+
revalidate,
72+
disabled,
73+
}: {
74+
queryKey: string;
75+
dispatcher: Dispatcher;
76+
hasState: boolean;
77+
revalidate?: boolean;
78+
disabled?: boolean;
79+
}) => {
80+
if (!revalidate && hasState) {
81+
return false;
82+
}
83+
84+
const queue = dispatcher.getQueue(queryKey);
85+
const isInitiallyLoading = dispatcher.hasRunningRequests(queryKey) || (!queue.stopped && disabled === false);
86+
87+
return isInitiallyLoading;
88+
};
89+
90+
export const getInitialState = <T extends RequestInstance>({
91+
initialResponse,
92+
dispatcher,
93+
request,
94+
disabled,
95+
revalidate,
96+
}: {
97+
initialResponse: NullableType<Partial<ExtractAdapterReturnType<T>>>;
98+
dispatcher: Dispatcher;
99+
request: T;
100+
/**
101+
* useFetch only
102+
*/
103+
disabled?: boolean;
104+
/**
105+
* useFetch only
106+
*/
107+
revalidate?: boolean;
108+
}): UseTrackedStateType<T> => {
72109
const { client, cacheKey, responseMapper } = request;
73110
const { cache } = client;
74111

@@ -77,8 +114,15 @@ export const getInitialState = <T extends RequestInstance>(
77114
ExtractErrorType<T>,
78115
ExtractAdapterExtraType<ExtractAdapterType<T>>
79116
>(cacheKey);
80-
const cacheState = getValidCacheData<T>(request, initialData, cacheData);
81-
const initialLoading = dispatcher.hasRunningRequests(request.queueKey);
117+
const cacheState = getValidCacheData<T>(request, initialResponse, cacheData);
118+
119+
const initialLoading = getIsInitiallyLoading({
120+
queryKey: request.queueKey,
121+
dispatcher,
122+
disabled,
123+
revalidate,
124+
hasState: !!cacheState,
125+
});
82126

83127
if (cacheState) {
84128
const mappedData = responseMapper ? responseMapper(cacheState) : cacheState;

β€Žpackages/react/src/hooks/use-fetch/use-fetch.hooks.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export const useFetch = <RequestType extends RequestInstance>(
7171
initialData,
7272
deepCompare,
7373
dependencyTracking,
74+
disabled,
75+
revalidate,
7476
});
7577

7678
/**
@@ -94,11 +96,6 @@ export const useFetch = <RequestType extends RequestInstance>(
9496
if (!disabled) {
9597
logger.debug(`Fetching data`);
9698
dispatcher.add(request);
97-
const queue = dispatcher.getQueue(request.queueKey);
98-
// We want fast initial loading state if not paused / disabled
99-
if (!queue.stopped && queue.requests.length) {
100-
actions.setLoading(true);
101-
}
10299
} else {
103100
logger.debug(`Cannot add to fetch queue`, { disabled });
104101
}

0 commit comments

Comments
Β (0)