Skip to content

Commit

Permalink
fix: make sure state is not updated when unmounted (#1758)
Browse files Browse the repository at this point in the history
  • Loading branch information
boschni committed Feb 6, 2021
1 parent 79e1c17 commit 5bd3fa9
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 11 deletions.
13 changes: 11 additions & 2 deletions src/react/useBaseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from 'react'

import { QueryObserverResult } from '../core/types'
import { notifyManager } from '../core/notifyManager'
import { QueryObserver } from '../core/queryObserver'
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
import { useQueryClient } from './QueryClientProvider'
import { UseBaseQueryOptions } from './types'
import { useIsMounted } from './useIsMounted'

export function useBaseQuery<TQueryFnData, TError, TData, TQueryData>(
options: UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData>,
Observer: typeof QueryObserver
) {
const isMounted = useIsMounted()
const queryClient = useQueryClient()
const errorResetBoundary = useQueryErrorResetBoundary()
const defaultedOptions = queryClient.defaultQueryObserverOptions(options)
Expand Down Expand Up @@ -63,8 +66,14 @@ export function useBaseQuery<TQueryFnData, TError, TData, TQueryData>(
// Subscribe to the observer
React.useEffect(() => {
errorResetBoundary.clearReset()
return observer.subscribe(notifyManager.batchCalls(setCurrentResult))
}, [observer, errorResetBoundary])
return observer.subscribe(
notifyManager.batchCalls((result: QueryObserverResult) => {
if (isMounted()) {
setCurrentResult(result)
}
})
)
}, [observer, errorResetBoundary, isMounted])

// Handle suspense
if (observer.options.suspense || observer.options.useErrorBoundary) {
Expand Down
12 changes: 8 additions & 4 deletions src/react/useIsFetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { notifyManager } from '../core/notifyManager'
import { QueryKey } from '../core/types'
import { parseFilterArgs, QueryFilters } from '../core/utils'
import { useQueryClient } from './QueryClientProvider'
import { useIsMounted } from './useIsMounted'

export function useIsFetching(filters?: QueryFilters): number
export function useIsFetching(
Expand All @@ -14,6 +15,7 @@ export function useIsFetching(
arg1?: QueryKey | QueryFilters,
arg2?: QueryFilters
): number {
const isMounted = useIsMounted()
const queryClient = useQueryClient()
const [filters] = parseFilterArgs(arg1, arg2)
const [isFetching, setIsFetching] = React.useState(
Expand All @@ -29,13 +31,15 @@ export function useIsFetching(
() =>
queryClient.getQueryCache().subscribe(
notifyManager.batchCalls(() => {
const newIsFetching = queryClient.isFetching(filtersRef.current)
if (isFetchingRef.current !== newIsFetching) {
setIsFetching(newIsFetching)
if (isMounted()) {
const newIsFetching = queryClient.isFetching(filtersRef.current)
if (isFetchingRef.current !== newIsFetching) {
setIsFetching(newIsFetching)
}
}
})
),
[queryClient]
[queryClient, isMounted]
)

return isFetching
Expand Down
17 changes: 17 additions & 0 deletions src/react/useIsMounted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

import { isServer } from '../core/utils'

export function useIsMounted() {
const mountedRef = React.useRef(false)
const isMounted = React.useCallback(() => mountedRef.current, [])

React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
mountedRef.current = true
return () => {
mountedRef.current = false
}
}, [])

return isMounted
}
7 changes: 4 additions & 3 deletions src/react/useMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MutationKey,
MutationObserverResult,
} from '../core/types'
import { useIsMounted } from './useIsMounted'

// HOOK

Expand Down Expand Up @@ -68,6 +69,7 @@ export function useMutation<
| UseMutationOptions<TData, TError, TVariables, TContext>,
arg3?: UseMutationOptions<TData, TError, TVariables, TContext>
): UseMutationResult<TData, TError, TVariables, TContext> {
const isMounted = useIsMounted()
const options = parseMutationArgs(arg1, arg2, arg3)
const queryClient = useQueryClient()

Expand Down Expand Up @@ -96,14 +98,13 @@ export function useMutation<
(
result: MutationObserverResult<TData, TError, TVariables, TContext>
) => {
// Check if the component is still mounted
if (observer.hasListeners()) {
if (isMounted()) {
setCurrentResult(result)
}
}
)
),
[observer]
[observer, isMounted]
)

const mutate = React.useCallback<
Expand Down
14 changes: 12 additions & 2 deletions src/react/useQueries.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react'

import { QueryObserverResult } from '../core/types'
import { notifyManager } from '../core/notifyManager'
import { QueriesObserver } from '../core/queriesObserver'
import { useQueryClient } from './QueryClientProvider'
import { UseQueryOptions, UseQueryResult } from './types'
import { useIsMounted } from './useIsMounted'

export function useQueries(queries: UseQueryOptions[]): UseQueryResult[] {
const isMounted = useIsMounted()
const queryClient = useQueryClient()

// Create queries observer
Expand All @@ -25,8 +28,15 @@ export function useQueries(queries: UseQueryOptions[]): UseQueryResult[] {

// Subscribe to the observer
React.useEffect(
() => observer.subscribe(notifyManager.batchCalls(setCurrentResult)),
[observer]
() =>
observer.subscribe(
notifyManager.batchCalls((result: QueryObserverResult[]) => {
if (isMounted()) {
setCurrentResult(result)
}
})
),
[observer, isMounted]
)

return currentResult
Expand Down

2 comments on commit 5bd3fa9

@vercel
Copy link

@vercel vercel bot commented on 5bd3fa9 Feb 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@znareak
Copy link

@znareak znareak commented on 5bd3fa9 Mar 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened with the useIsMounted hook ?

Please sign in to comment.