-
-
Notifications
You must be signed in to change notification settings - Fork 454
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement immediate effect and refactor (#250)
* Add useRequest hook to simplify logic Currently we manually rely on request.key in useQuery to ensure that we don't trigger a new effect, when it's not necessary, which is error-prone. Instead this hook uses useMemo and checks request.key against the last request it generated. If the key hasn't changed it returns the previous reference. This ensures reference equality, which removes any check of request.key in useQuery * Add useImmediateEffect hook We want to execute an effect immediately in render on initial mount for suspense / SSR. This is a very simple hook that does this by abstracting effects in three phases: - Initial mount (render) - After mount - Render So on initial mount it will execute the effect immediately. Then it will prevent the first run of useEffect since the effect has already been called, then the effect will run as usual. * Fix missing return type in utils/request * Implement new helpers in useQuery * Fix useImmediateEffect teardown * Merge new useEffect and useImmediateEffect * Adjust useImmediateEffect naming * Fix initial teardown being ignored in useImmediateEffect * Update useQuery tests * Add tests for useImmediateEffect and useRequest * Add useRequest to useSubscription * Fix isMounted in hooks Co-authored-by: Jovi De Croock <[email protected]>
- Loading branch information
1 parent
1d278ca
commit 7717d01
Showing
9 changed files
with
156 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import { renderHook } from 'react-hooks-testing-library'; | ||
import { useImmediateEffect } from './useImmediateEffect'; | ||
|
||
it('calls effects immediately on mount', () => { | ||
const spy = jest.spyOn(React, 'useEffect'); | ||
const useEffect = jest.fn(); | ||
const effect = jest.fn(); | ||
|
||
spy.mockImplementation(useEffect); | ||
renderHook(() => useImmediateEffect(effect, [effect])); | ||
|
||
expect(effect).toHaveBeenCalledTimes(1); | ||
expect(effect).toHaveBeenCalledTimes(1); | ||
|
||
expect(useEffect).toHaveBeenCalledWith(expect.any(Function), [ | ||
expect.any(Function), | ||
]); | ||
|
||
useEffect.mockRestore(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useRef, useEffect, useCallback } from 'react'; | ||
import { noop } from '../utils'; | ||
|
||
enum LifecycleState { | ||
WillMount = 0, | ||
DidMount = 1, | ||
Update = 2, | ||
} | ||
|
||
type Effect = () => () => void; | ||
|
||
/** This executes an effect immediately on initial render and then treats it as a normal effect */ | ||
export const useImmediateEffect = ( | ||
effect: Effect, | ||
changes: ReadonlyArray<any> | ||
) => { | ||
const teardown = useRef(noop); | ||
const state = useRef(LifecycleState.WillMount); | ||
const execute = useCallback(effect, changes); | ||
|
||
// On initial render we just execute the effect | ||
if (state.current === LifecycleState.WillMount) { | ||
state.current = LifecycleState.DidMount; | ||
teardown.current = execute(); | ||
} | ||
|
||
useEffect(() => { | ||
// Initially we skip executing the effect since we've already done so on | ||
// initial render, then we execute it as usual | ||
if (state.current === LifecycleState.Update) { | ||
return (teardown.current = execute()); | ||
} else { | ||
state.current = LifecycleState.Update; | ||
return teardown.current; | ||
} | ||
}, [execute]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { renderHook } from 'react-hooks-testing-library'; | ||
import { queryGql } from '../test-utils'; | ||
import { useRequest } from './useRequest'; | ||
|
||
it('preserves instance of request when key has not changed', () => { | ||
let { query, variables } = queryGql; | ||
|
||
const { result, rerender } = renderHook( | ||
({ query, variables }) => useRequest(query, variables), | ||
{ initialProps: { query, variables } } | ||
); | ||
|
||
const resultA = result.current; | ||
expect(resultA).toEqual({ | ||
key: expect.any(Number), | ||
query: expect.anything(), | ||
variables: variables, | ||
}); | ||
|
||
variables = { ...variables }; // Change reference | ||
rerender({ query, variables }); | ||
|
||
const resultB = result.current; | ||
expect(resultA).toBe(resultB); | ||
|
||
variables = { ...variables, test: true }; // Change values | ||
rerender({ query, variables }); | ||
|
||
const resultC = result.current; | ||
expect(resultA).not.toBe(resultC); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { DocumentNode } from 'graphql'; | ||
import { useRef, useMemo } from 'react'; | ||
import { GraphQLRequest } from '../types'; | ||
import { createRequest } from '../utils'; | ||
|
||
/** Creates a request from a query and variables but preserves reference equality if the key isn't changing */ | ||
export const useRequest = ( | ||
query: string | DocumentNode, | ||
variables?: any | ||
): GraphQLRequest => { | ||
const prev = useRef<void | GraphQLRequest>(undefined); | ||
|
||
return useMemo(() => { | ||
const request = createRequest(query, variables); | ||
// We manually ensure reference equality if the key hasn't changed | ||
if (prev.current !== undefined && prev.current.key === request.key) { | ||
return prev.current; | ||
} else { | ||
prev.current = request; | ||
return request; | ||
} | ||
}, [query, variables]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters