Skip to content

Commit dbb8954

Browse files
committed
fix: Handle unmounting whilst request is in flight (#96)
Prevents `fetchData` within `useClientRequest` from updating state when a request returns, if the component has unmounted. closes #54 - [x] I have checked the [contributing document](../blob/master/CONTRIBUTING.md) - [x] I have added or updated any relevant documentation - [x] I have added or updated any relevant tests
1 parent 2027064 commit dbb8954

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

packages/graphql-hooks/src/useClientRequest.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function reducer(state, action) {
5252
*/
5353
function useClientRequest(query, initialOpts = {}) {
5454
const client = React.useContext(ClientContext)
55+
const isMounted = React.useRef(true)
5556
const operation = {
5657
query,
5758
variables: initialOpts.variables,
@@ -73,12 +74,22 @@ function useClientRequest(query, initialOpts = {}) {
7374
// if so the state would be invalid, this effect ensures we reset it back
7475
const stringifiedCacheKey = JSON.stringify(cacheKey)
7576
React.useEffect(() => {
76-
if (initialOpts.updateData) return // if using updateData we can assume that the consumer cares about the previous data
77-
dispatch({ type: actionTypes.RESET_STATE, initialState })
77+
if (!initialOpts.updateData) {
78+
// if using updateData we can assume that the consumer cares about the previous data
79+
dispatch({ type: actionTypes.RESET_STATE, initialState })
80+
}
7881
}, [stringifiedCacheKey]) // eslint-disable-line react-hooks/exhaustive-deps
7982

83+
React.useEffect(() => {
84+
isMounted.current = true
85+
return () => {
86+
isMounted.current = false
87+
}
88+
}, [])
89+
8090
// arguments to fetchData override the useClientRequest arguments
8191
function fetchData(newOpts) {
92+
if (!isMounted.current) return Promise.resolve()
8293
const revisedOpts = {
8394
...initialOpts,
8495
...newOpts
@@ -118,10 +129,12 @@ function useClientRequest(query, initialOpts = {}) {
118129
client.cache.set(revisedCacheKey, result)
119130
}
120131

121-
dispatch({
122-
type: actionTypes.REQUEST_RESULT,
123-
result
124-
})
132+
if (isMounted.current) {
133+
dispatch({
134+
type: actionTypes.REQUEST_RESULT,
135+
result
136+
})
137+
}
125138

126139
return result
127140
})

packages/graphql-hooks/test/unit/useClientRequest.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,50 @@ describe('useClientRequest', () => {
205205
expect(state).toEqual({ cacheHit: false, loading: false, data: 'data' })
206206
})
207207

208+
it('calls request with options & doesnt update state if component unmounts whilst request is in flight', async () => {
209+
let fetchData, state
210+
const { unmount } = renderHook(
211+
() =>
212+
([fetchData, state] = useClientRequest(TEST_QUERY, {
213+
variables: { limit: 2 },
214+
operationName: 'test'
215+
})),
216+
{
217+
wrapper: Wrapper
218+
}
219+
)
220+
221+
const fetchDataPromise = fetchData()
222+
unmount()
223+
await fetchDataPromise
224+
225+
expect(mockClient.request).toHaveBeenCalledWith(
226+
{ operationName: 'test', variables: { limit: 2 }, query: TEST_QUERY },
227+
{ operationName: 'test', variables: { limit: 2 } }
228+
)
229+
expect(state).toEqual({ cacheHit: false, loading: true })
230+
})
231+
232+
it('returns undefined instantly if not mounted', async () => {
233+
let fetchData, state
234+
const { unmount } = renderHook(
235+
() =>
236+
([fetchData, state] = useClientRequest(TEST_QUERY, {
237+
variables: { limit: 2 },
238+
operationName: 'test'
239+
})),
240+
{
241+
wrapper: Wrapper
242+
}
243+
)
244+
245+
unmount()
246+
const result = await fetchData()
247+
expect(result).toBe(undefined)
248+
expect(mockClient.request).not.toHaveBeenCalled()
249+
expect(state).toEqual({ cacheHit: false, loading: true })
250+
})
251+
208252
it('calls request with revised options', async () => {
209253
let fetchData
210254
renderHook(

0 commit comments

Comments
 (0)