Skip to content
This repository was archived by the owner on Jan 2, 2021. It is now read-only.

Commit 7b90647

Browse files
committed
fix: useSafeState
1 parent 721b7e6 commit 7b90647

3 files changed

Lines changed: 61 additions & 7 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"workbench.colorCustomizations": {
3-
"titleBar.activeBackground": "#ff4154", // change this color!
4-
"titleBar.inactiveBackground": "#ff4154", // change this color!
3+
"titleBar.activeBackground": "#00ac4e", // change this color!
4+
"titleBar.inactiveBackground": "#00ac4e", // change this color!
55
"titleBar.activeForeground": "#ffffff", // change this color!
66
"titleBar.inactiveForeground": "#ffffff" // change this color!
77
}

src/index.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import match from 'match-sorter'
33
import { queryCache as cache, useQueryCache } from 'react-query'
44
import useLocalStorage from './useLocalStorage'
5+
import { useSafeState } from './utils'
56

67
//
78

@@ -67,11 +68,11 @@ export function ReactQueryDevtools({
6768
'reactQueryDevtoolsOpen',
6869
initialIsOpen
6970
)
70-
const [isResolvedOpen, setIsResolvedOpen] = React.useState(false)
71+
const [isResolvedOpen, setIsResolvedOpen] = useSafeState(false)
7172

7273
React.useEffect(() => {
7374
setIsResolvedOpen(isOpen)
74-
}, [isOpen, isResolvedOpen])
75+
}, [isOpen, isResolvedOpen, setIsResolvedOpen])
7576

7677
React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
7778
if (isResolvedOpen) {
@@ -241,7 +242,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef(
241242
false
242243
)
243244

244-
const [isDragging, setIsDragging] = React.useState(false)
245+
const [isDragging, setIsDragging] = useSafeState(false)
245246

246247
const sortFn = React.useMemo(() => sortFns[sort], [sort])
247248

@@ -281,7 +282,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef(
281282
setIsDragging(false)
282283
}
283284

284-
const [unsortedQueries, setUnsortedQueries] = React.useState(
285+
const [unsortedQueries, setUnsortedQueries] = useSafeState(
285286
Object.values(queryCache.queries)
286287
)
287288

@@ -321,7 +322,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef(
321322
return queryCache.subscribe(queryCache => {
322323
setUnsortedQueries(Object.values(queryCache.queries))
323324
})
324-
}, [sort, sortFn, sortDesc, queryCache])
325+
}, [sort, sortFn, sortDesc, queryCache, setUnsortedQueries])
325326

326327
return (
327328
<ThemeProvider theme={theme}>

src/utils.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import React from 'react'
33
import { useTheme } from './theme'
44
import useMediaQuery from './useMediaQuery'
55

6+
export const isServer = typeof window === 'undefined'
7+
68
export function getQueryStatusColor(query, theme) {
79
return query.state.isFetching
810
? theme.active
@@ -54,3 +56,54 @@ export function styled(type, newStyles, queries = {}) {
5456
})
5557
})
5658
}
59+
60+
function useIsMounted() {
61+
const mountedRef = React.useRef(false)
62+
const isMounted = React.useCallback(() => mountedRef.current, [])
63+
64+
React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
65+
mountedRef.current = true
66+
return () => {
67+
mountedRef.current = false
68+
}
69+
}, [])
70+
71+
return isMounted
72+
}
73+
74+
/**
75+
* This hook is a safe useState version which schedules state updates in microtasks
76+
* to prevent updating a component state while React is rendering different components
77+
* or when the component is not mounted anymore.
78+
*/
79+
export function useSafeState(initialState) {
80+
const isMounted = useIsMounted()
81+
const [state, setState] = React.useState(initialState)
82+
83+
const safeSetState = React.useCallback(
84+
value => {
85+
scheduleMicrotask(() => {
86+
if (isMounted()) {
87+
setState(value)
88+
}
89+
})
90+
},
91+
[isMounted]
92+
)
93+
94+
return [state, safeSetState]
95+
}
96+
97+
/**
98+
* Schedules a microtask.
99+
* This can be useful to schedule state updates after rendering.
100+
*/
101+
function scheduleMicrotask(callback) {
102+
Promise.resolve()
103+
.then(callback)
104+
.catch(error =>
105+
setTimeout(() => {
106+
throw error
107+
})
108+
)
109+
}

0 commit comments

Comments
 (0)