Skip to content
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

Merged
merged 5 commits into from
May 19, 2019
Merged

Conversation

parkerziegler
Copy link
Contributor

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.

Copy link
Member

@kitten kitten left a 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 🙏

@@ -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) {
Copy link
Member

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 🤔

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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.

@@ -28,6 +29,10 @@ class QueryHandler extends Component<QueryHandlerProps, QueryHandlerState> {
executeQuery = (opts?: Partial<OperationContext>) => {
this.unsubscribe();

if (this.props.skip) {
Copy link
Contributor Author

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!());
Copy link
Contributor Author

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.

@parkerziegler
Copy link
Contributor Author

@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) {
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

Copy link
Contributor

@andyrichardson andyrichardson May 15, 2019

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.

Copy link
Contributor Author

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]);
Copy link
Contributor Author

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?

Copy link
Contributor Author

@parkerziegler parkerziegler May 15, 2019

Choose a reason for hiding this comment

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

Copy link
Member

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 👍

Copy link
Contributor Author

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 😄

Copy link
Contributor

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 👍

@parkerziegler
Copy link
Contributor Author

@kitten @andyrichardson how do you feel about merging this? I have some follow up changes that 1) integrate typescript-eslint just for the React hooks rules, 2) fix some dependency array issues flagged by the ESLint plugin, and 3) show what a test suite for useQuery would look like with react-hooks-testing-library. Would love to get those changes up for review soon!

@parkerziegler parkerziegler merged commit a255b46 into master May 19, 2019
@parkerziegler parkerziegler deleted the task/228-add-skip branch May 19, 2019 01:39
@andyrichardson
Copy link
Contributor

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.

@kitten
Copy link
Member

kitten commented May 19, 2019

@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.

@andyrichardson
Copy link
Contributor

andyrichardson commented May 20, 2019

@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

@matheus-rosin
Copy link

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 prevent or even block. I rather the first one, but the second seems decent too.

@good-idea
Copy link
Contributor

Any idea on when this update will be published to NPM?

@parkerziegler
Copy link
Contributor Author

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants