-
-
Notifications
You must be signed in to change notification settings - Fork 454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for skip to Query and useQuery. #237
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cheers for picking this up, Parker 🙏
src/components/Query.tsx
Outdated
@@ -58,7 +63,11 @@ class QueryHandler extends Component<QueryHandlerProps, QueryHandlerState> { | |||
this.executeQuery(); | |||
} | |||
|
|||
componentDidUpdate() { | |||
componentDidUpdate(prevProps: QueryHandlerProps) { | |||
if (this.props.skip && prevProps.skip !== this.props.skip) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If props.skip
is true
here, we'll never reach this.unsubscribe
above 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah gotcha. So we'd hit a case where we have a current query subscription, skip
updates to true
, this block gets hit and this.unsubscribe
never gets called. What if we just call this.unsubscribe()
before the early return here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, that'd be equivalent but it should always be preferred to just call execute again since it always unsubscribes and resubscribes if necessary, so that might make things easier on terms of it always aligning the subscription with the props
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, good point. Let me do some refactoring and re-push.
src/components/Query.tsx
Outdated
@@ -28,6 +29,10 @@ class QueryHandler extends Component<QueryHandlerProps, QueryHandlerState> { | |||
executeQuery = (opts?: Partial<OperationContext>) => { | |||
this.unsubscribe(); | |||
|
|||
if (this.props.skip) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per your suggestions @kitten moved this after the call to unsubscribe
. Moreover, this would return before fetching
ever gets set to true
, so I don't think we need to handle resetting fetching
to false
.
@@ -158,7 +164,21 @@ describe('on change', () => { | |||
describe('execute query', () => { | |||
it('triggers query execution', () => { | |||
renderer.create(<QueryUser {...props} />); | |||
act(() => execute()); | |||
act(() => execute!()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-nullary operator for tests, since execute
can, from a type perspective, be undefined
in the course of running these tests.
@kitten @andyrichardson refactored this just now per @kitten's suggestions, let me know what the next steps you think we should take are 😄 Thanks for the review thus far 💯 |
); | ||
let teardown = noop; | ||
|
||
if (!this.props.skip) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving the check here ensures that both this.unsubscribe
will get executed and redefined in preparation for the next query. No more futzing with componentDidUpdate
😅
if (!args.skip) { | ||
[teardown] = pipe( | ||
client.executeQuery(request, { | ||
requestPolicy: args.requestPolicy, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this now, shouldn't args.requestPolicy
also be included in the dependency array for this useCallback
? Otherwise we might not recompute it if it changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup I believe so. We're currently allowing users to specify the request policy on a manual query execution but I believe this goes against the way hooks work.
If the request policy changes, that should be reflected in the components state and flow all the way down to the useQuery hook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed!
); | ||
|
||
useEffect(() => { | ||
executeQuery(); | ||
return unsubscribe; | ||
}, [request.key]); | ||
}, [request.key, args.skip]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last question here for both of you @andyrichardson and @kitten -> should executeQuery
be included in the dependency array here? The Hooks FAQ make a big deal about it being unsafe to exclude functions that reference props
/ state
from the dependency array of useEffect
calls. So basically this dep array would become just [executeQuery]
. I'm also starting to think we might want to useRef
unsubscribe
. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooks FAQ on functions as dependencies: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, didn't get to this 😱 Including it into the array here will probably mean that requestPolicy
can change at any time which is generally good 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🆒 that'll come up in the next PR, gonna merge this as is 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@parkerziegler - yeah I think that is necessary 👍
@kitten @andyrichardson how do you feel about merging this? I have some follow up changes that 1) integrate |
PR looks strong 👍👍👍 @parkerzielger minor suggestion about the wording, maybe wording could be more specific than 'skip'? I'm thinking 'autoFetch' or 'fetchOnMount'. Not a big deal either way - just my 2 cents. Is 'cacheOnly' the same as skip? In theory I would expect cacheOnly to not fetch on mount but still fetch when calling the refetch function. I had this problem with Tipple and settled on allowing both options despite the redundancy. Also, +1 on migrating to eslint. The react linting rules suck at times (like if you want an effect to be called exclusively on mount - this would be a linting error) and adding a bunch of ignores to valid use cases is a pain but that's better than missing a required dependency. |
@andyrichardson I think auto fetch or fetch on mount could imply that we encourage hooks to remain passive on mount which is regarded as an antipattern by the React team. For now this naming was suggested because it's also what Apollo uses I suppose, and it nicely matches up with the existing skip directive. But yea, not a fan. It's not the same as cache Only as it'll stop to fetch at all. So it is useful to "freeze" the directives logic completely. |
@kitten what does "passive on mount" mean? Do you have a link to the react docs about the antipattern - I need to read up on this 📖 If in getting this right, you're proposing that manual refetch isn't possible at all when using cache only? If we're going this route we'll need to update our types for useQuery |
About the word "skip", when I suggested it, I didn't even wonder if that is the most plausible 😄 (yeah, it was based on Apollo already available nomenclature). So, if I could suggest a better word (and more JS-ish), it would be |
Any idea on when this update will be published to NPM? |
@good-idea #239 will have this, which should get merged today. I'd guess we'd be able to release it next week. I'll follow up w/ Phil there and we'll try to ship it soon! |
This PR picks up on the changes in #229 and would close issue #228. @kitten I read through your comments in #229 and attempted to address them in this PR. I also added a few tests to ensure things are behaving as expected, but definitely would appreciate both of your 👀 in verifying that this behaves as expected.