Skip to content

Commit 69a270d

Browse files
committed
automatically refetch if focus changed
1 parent 9569a58 commit 69a270d

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-api-fetching",
3-
"version": "0.2.1",
3+
"version": "0.3.0",
44
"description": "Make fetching API easier with React's hooks + context",
55
"source": "src/index.tsx",
66
"main": "dist/main.js",

src/index.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef } from 'react'
22
import { deepEqual, deepMerge, generateConcurrentFn } from './utils'
3-
import { useValueRef, useEnhancedMemo, useCachedData } from './hooks'
3+
import { useValueRef, useEnhancedMemo, useCachedData, useEnhancedEffect } from './hooks'
44
import { ApiResult, Cache, defaultResult, generateCacheKey, isCached } from './cache'
55
export { Cache } from './cache'
66

77
export type ApiVariables<T extends Partial<Record<'body' | 'query' | 'body', any>> = {}> = T
88

99
interface ApiConfig<APIs> {
1010
fetcher: (api: APIs[keyof APIs], variables: ApiVariables) => Promise<unknown>
11+
refetchOnFocus?: boolean
12+
useFocused?: () => boolean
1113
}
1214

1315
interface ApiContext<APIs> {
@@ -20,13 +22,21 @@ type ApiProviderProps<T> = React.PropsWithChildren<{
2022
config: ApiConfig<T>
2123
}>
2224

25+
function useDefaultFocused() {
26+
return true
27+
}
28+
2329
function createProvider<T>(ctx: React.Context<ApiContext<T>>) {
2430
return function Provider({
2531
config,
2632
children,
2733
cache = new Cache(),
2834
}: ApiProviderProps<T>) {
29-
const { fetcher } = config
35+
const {
36+
fetcher,
37+
refetchOnFocus = true,
38+
useFocused = useDefaultFocused
39+
} = config
3040
const concurrentFetcher = useMemo(() => generateConcurrentFn(fetcher), [fetcher])
3141

3242
return (
@@ -35,6 +45,8 @@ function createProvider<T>(ctx: React.Context<ApiContext<T>>) {
3545
cache,
3646
config: {
3747
...config,
48+
useFocused,
49+
refetchOnFocus,
3850
fetcher: concurrentFetcher
3951
}
4052
}}
@@ -59,6 +71,7 @@ function partialStateReducer<T extends {}>(s: T, action: Partial<T>) {
5971

6072
export interface UseLazyApiOptions<TData, TError, TVariables> {
6173
cached?: boolean
74+
refetchOnFocus?: boolean
6275
variables?: TVariables
6376
onFetch?: () => Promise<any>
6477
onCompleted?: (params: { data: TData | null, error: TError | null }) => Promise<any>
@@ -76,17 +89,19 @@ function createUseLazyApi<
7689
TApiError extends TError[K],
7790
TApiVariables extends TVariables[K]
7891
>(key: K, defaultOpts: UseLazyApiOptions<TApiData, TApiError, TApiVariables> = {}) {
92+
const fetchedRef = useRef(false)
7993
const defaultOptsRef = useValueRef(defaultOpts)
8094
const {
8195
cache,
82-
config: { fetcher }
96+
config: { fetcher, refetchOnFocus, useFocused }
8397
} = useContext(ctx)
8498
const [variables, setVariables] = useReducer(stateReducer as typeof stateReducer<TApiVariables>, (defaultOpts.variables || {}) as TApiVariables)
8599
const variablesRef = useValueRef(variables)
86100
const cacheKey = useMemo(() => generateCacheKey(key, variables), [key, variables])
87101
const cachedResult = useCachedData(cacheKey, cache) as ApiResult<TApiData, TApiError>
88102
const [localResult, setLocalResult] = useReducer(partialStateReducer as typeof partialStateReducer<ApiResult<TApiData, TApiError>>, defaultResult)
89103
const prevResultRef = useRef({})
104+
const prevVariablesRef = useRef<TApiVariables>()
90105
const cachedRef = useRef(defaultOpts.cached ?? true)
91106

92107
const fetch = useCallback(async (opts: Pick<UseLazyApiOptions<TApiData, TApiError, TApiVariables>, 'variables'> = {}) => {
@@ -127,7 +142,9 @@ function createUseLazyApi<
127142

128143
if (onCompleted) await onCompleted({ data, error })
129144

145+
fetchedRef.current = true
130146
prevResultRef.current = { data, error }
147+
prevVariablesRef.current = variables as TApiVariables
131148

132149
if (cached) {
133150
cache.set(cacheKey, {
@@ -146,13 +163,24 @@ function createUseLazyApi<
146163
return { data, error }
147164
}, [key, cache, fetcher, setVariables, setLocalResult])
148165

166+
const refetch = useCallback(() => fetch({ variables: prevVariablesRef.current }), [fetch])
167+
168+
/**
169+
* automatically refetch on focus
170+
*/
171+
const focused = useFocused!()
172+
const refetchOnFocusRef = useRef(defaultOpts.refetchOnFocus ?? refetchOnFocus)
173+
useEnhancedEffect(() => {
174+
if (focused && refetchOnFocusRef.current && fetchedRef.current) refetch()
175+
}, [focused, refetch])
176+
149177
return [fetch, {
150178
...(cachedRef.current ? {
151179
...cachedResult,
152180
// when we changed variables, the cached result might be empty, but we want to keep showing the previous result
153181
...(!isCached(cacheKey, cache) && prevResultRef.current),
154182
} : localResult),
155-
refetch: fetch
183+
refetch
156184
}] as const
157185
}
158186
}
@@ -172,7 +200,8 @@ function createUseMutationApi<
172200
>(key: K, opts: UseLazyApiOptions<TApiData, TApiError, TApiVariables> = {}) {
173201
return useLazyApi<K, TApiData, TApiError, TApiVariables>(key, {
174202
...opts,
175-
cached: false
203+
cached: false,
204+
refetchOnFocus: false
176205
})
177206
}
178207
}

0 commit comments

Comments
 (0)