Skip to content

Commit 09af634

Browse files
authored
Replace useImmediate* hooks with react-wonka (#447)
* Add react-wonka * Rewrite useQuery using react-wonka's useSubjectValue * Rewrite useSubscription using react-wonka's useSubjectValue * Remove useImmediate* hooks and update useMutation * Remove react-wonka from externals It's probably fair to bundle this for now. It's tiny and this way we can compare the bundlesize to before (+.02kB min+gzip) * Refactor useQuery / useSubscription This gets rid of some of the internal structures and functions in favour of a simpler Source memo. * Replace some things for more code reuse in useQuery/useSubscription
1 parent d327e3d commit 09af634

11 files changed

+132
-309
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
"react-dom": ">= 16.8.0"
134134
},
135135
"dependencies": {
136+
"react-wonka": "^1.0.1",
136137
"wonka": "^3.2.1"
137138
}
138139
}

rollup.config.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import { terser } from 'rollup-plugin-terser';
99
import transformPipe from './scripts/transform-pipe';
1010

1111
const pkgInfo = require('./package.json');
12-
const external = ['dns', 'fs', 'path', 'url'];
1312

14-
if (pkgInfo.peerDependencies) {
13+
let external = ['dns', 'fs', 'path', 'url'];
14+
if (pkgInfo.peerDependencies)
1515
external.push(...Object.keys(pkgInfo.peerDependencies));
16-
}
17-
18-
if (pkgInfo.dependencies) {
16+
if (pkgInfo.dependencies)
1917
external.push(...Object.keys(pkgInfo.dependencies));
20-
}
18+
19+
external = external.filter(x => x !== 'react-wonka');
2120

2221
const externalPredicate = new RegExp(`^(${external.join('|')})($|/)`);
2322
const externalTest = id => {

src/hooks/useImmediateEffect.test.ts

-44
This file was deleted.

src/hooks/useImmediateEffect.ts

-30
This file was deleted.

src/hooks/useImmediateState.test.tsx

-53
This file was deleted.

src/hooks/useImmediateState.ts

-50
This file was deleted.

src/hooks/useMutation.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { DocumentNode } from 'graphql';
2-
import { useCallback } from 'react';
2+
import { useState, useCallback } from 'react';
33
import { pipe, toPromise } from 'wonka';
44
import { useClient } from '../context';
55
import { OperationResult, OperationContext } from '../types';
66
import { CombinedError, createRequest } from '../utils';
7-
import { useImmediateState } from './useImmediateState';
87

98
export interface UseMutationState<T> {
109
fetching: boolean;
@@ -25,7 +24,8 @@ export const useMutation = <T = any, V = object>(
2524
query: DocumentNode | string
2625
): UseMutationResponse<T, V> => {
2726
const client = useClient();
28-
const [state, setState] = useImmediateState<UseMutationState<T>>({
27+
28+
const [state, setState] = useState<UseMutationState<T>>({
2929
fetching: false,
3030
error: undefined,
3131
data: undefined,

src/hooks/useQuery.ts

+54-48
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { DocumentNode } from 'graphql';
2-
import { useCallback, useRef } from 'react';
3-
import { pipe, onEnd, subscribe } from 'wonka';
2+
import { useCallback, useMemo } from 'react';
3+
import { pipe, concat, fromValue, switchMap, map, scan } from 'wonka';
4+
import { useSubjectValue } from 'react-wonka';
5+
46
import { useClient } from '../context';
57
import { OperationContext, RequestPolicy } from '../types';
6-
import { CombinedError, noop } from '../utils';
8+
import { CombinedError } from '../utils';
79
import { useRequest } from './useRequest';
8-
import { useImmediateEffect } from './useImmediateEffect';
9-
import { useImmediateState } from './useImmediateState';
10+
11+
const initialState: UseQueryState<any> = {
12+
fetching: false,
13+
data: undefined,
14+
error: undefined,
15+
extensions: undefined,
16+
};
1017

1118
export interface UseQueryArgs<V> {
1219
query: string | DocumentNode;
@@ -32,61 +39,60 @@ export type UseQueryResponse<T> = [
3239
export const useQuery = <T = any, V = object>(
3340
args: UseQueryArgs<V>
3441
): UseQueryResponse<T> => {
35-
const unsubscribe = useRef(noop);
3642
const client = useClient();
3743

38-
// This is like useState but updates the state object
39-
// immediately, when we're still before the initial mount
40-
const [state, setState] = useImmediateState<UseQueryState<T>>({
41-
fetching: false,
42-
data: undefined,
43-
error: undefined,
44-
extensions: undefined,
45-
});
46-
4744
// This creates a request which will keep a stable reference
4845
// if request.key doesn't change
4946
const request = useRequest(args.query, args.variables);
5047

51-
const executeQuery = useCallback(
48+
// Create a new query-source from client.executeQuery
49+
const makeQuery$ = useCallback(
5250
(opts?: Partial<OperationContext>) => {
53-
unsubscribe.current();
51+
return client.executeQuery(request, {
52+
requestPolicy: args.requestPolicy,
53+
pollInterval: args.pollInterval,
54+
...args.context,
55+
...opts,
56+
});
57+
},
58+
[client, request, args.requestPolicy, args.pollInterval, args.context]
59+
);
5460

55-
setState(s => ({ ...s, fetching: true }));
61+
const [state, update] = useSubjectValue(
62+
query$$ =>
63+
pipe(
64+
query$$,
65+
switchMap(query$ => {
66+
if (!query$) return fromValue({ fetching: false });
5667

57-
[unsubscribe.current] = pipe(
58-
client.executeQuery(request, {
59-
requestPolicy: args.requestPolicy,
60-
pollInterval: args.pollInterval,
61-
...args.context,
62-
...opts,
68+
return concat([
69+
// Initially set fetching to true
70+
fromValue({ fetching: true }),
71+
pipe(
72+
query$,
73+
map(({ data, error, extensions }) => ({
74+
fetching: false,
75+
data,
76+
error,
77+
extensions,
78+
}))
79+
),
80+
// When the source proactively closes, fetching is set to false
81+
fromValue({ fetching: false }),
82+
]);
6383
}),
64-
onEnd(() => setState(s => ({ ...s, fetching: false }))),
65-
subscribe(({ data, error, extensions }) => {
66-
setState({ fetching: false, data, error, extensions });
67-
})
68-
);
69-
},
70-
[
71-
args.context,
72-
args.requestPolicy,
73-
args.pollInterval,
74-
client,
75-
request,
76-
setState,
77-
]
84+
// The individual partial results are merged into each previous result
85+
scan((result, partial) => ({ ...result, ...partial }), initialState)
86+
),
87+
useMemo(() => (args.pause ? null : makeQuery$()), [args.pause, makeQuery$]),
88+
initialState
7889
);
7990

80-
useImmediateEffect(() => {
81-
if (args.pause) {
82-
unsubscribe.current();
83-
setState(s => ({ ...s, fetching: false }));
84-
return noop;
85-
}
86-
87-
executeQuery();
88-
return () => unsubscribe.current(); // eslint-disable-line
89-
}, [executeQuery, args.pause, setState]);
91+
// This is the imperative execute function passed to the user
92+
const executeQuery = useCallback(
93+
(opts?: Partial<OperationContext>) => update(makeQuery$(opts)),
94+
[update, makeQuery$]
95+
);
9096

9197
return [state, executeQuery];
9298
};

0 commit comments

Comments
 (0)