diff --git a/.changeset/lovely-impalas-lay.md b/.changeset/lovely-impalas-lay.md new file mode 100644 index 0000000000..a799e62bdc --- /dev/null +++ b/.changeset/lovely-impalas-lay.md @@ -0,0 +1,14 @@ +--- +'graphql-fixtures': minor +'graphql-mini-transforms': minor +'@shopify/jest-dom-mocks': minor +'@shopify/react-async': minor +'@shopify/react-form-state': minor +'@shopify/react-graphql': minor +'@shopify/react-idle': minor +'@shopify/react-import-remote': minor +'@shopify/react-server': minor +'@shopify/react-testing': minor +--- + +Update types to account changes in TypeScript 4.8 and 4.9. [Propogate contstraints on generic types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#unconstrained-generics-no-longer-assignable-to) and update type usage relating to `Window` and `Navigator`. Technically this makes some types stricter, as attempting to pass `null|undefined` into certain functions is now disallowed by TypeScript, but these were never expected runtime values in the first place. diff --git a/.changeset/purple-pumpkins-cheer.md b/.changeset/purple-pumpkins-cheer.md new file mode 100644 index 0000000000..3905d70f56 --- /dev/null +++ b/.changeset/purple-pumpkins-cheer.md @@ -0,0 +1,5 @@ +--- +'@shopify/react-graphql': minor +--- + +`extends {}` has been added to the `Data` / `Variables` / `DeepPartial` generic types on the `useQuery` and `useGraphQLDocument` hooks, the `createAsyncQuery`,`createAsyncQueryComponent` functions and the `Query` component. If you use typescript's `strictNullChecks` option and define functions that contain generics that are then passed into any of these functions you may need add an `extends {}` to your code as well, per [the typescript 4.8 changelog](https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#unconstrained-generics-no-longer-assignable-to). For example, replace `` with ``. diff --git a/.changeset/rotten-swans-lay.md b/.changeset/rotten-swans-lay.md new file mode 100644 index 0000000000..488d442e0b --- /dev/null +++ b/.changeset/rotten-swans-lay.md @@ -0,0 +1,6 @@ +--- +'@shopify/react-idle': patch +'@shopify/react-import-remote': patch +--- + +Remove dependency on `@shopify/useful-types` diff --git a/package.json b/package.json index 04dcbf063c..bed45b0a39 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "react18": "npm:react@^18.1.0", "rimraf": "^2.6.2", "tsd": "^0.19.1", - "typescript": "~4.7.4", + "typescript": "~4.9.3", "yalc": "^1.0.0-pre.50" } } diff --git a/packages/graphql-fixtures/src/fill.ts b/packages/graphql-fixtures/src/fill.ts index 08890b353f..e47a3a94fb 100644 --- a/packages/graphql-fixtures/src/fill.ts +++ b/packages/graphql-fixtures/src/fill.ts @@ -95,6 +95,10 @@ export type DeepThunk = | null | undefined; +// The `undefined extends Variables ? {} : Variables` dance is needed to coerce +// variables that are undefined to an empty object, so that it matches the shape +// of `GraphQLOperation`, because that has a default value of `{}` on the +// variables generic type. export type GraphQLFillerData = Operation extends GraphQLOperation< infer Data, @@ -102,9 +106,14 @@ export type GraphQLFillerData = infer PartialData > ? Thunk< - DeepThunk, + DeepThunk< + PartialData, + Data, + undefined extends Variables ? {} : Variables, + PartialData + >, Data, - Variables, + undefined extends Variables ? {} : Variables, PartialData > : never; @@ -149,7 +158,11 @@ export function createFiller( const context = {schema, resolvers}; - return function fill( + return function fill< + Data extends {}, + Variables extends {}, + PartialData extends {}, + >( _document: GraphQLOperation, data?: GraphQLFillerData>, ): (request: GraphQLRequest) => Data { diff --git a/packages/graphql-mini-transforms/src/document.ts b/packages/graphql-mini-transforms/src/document.ts index 3c154800b1..771bca4b05 100644 --- a/packages/graphql-mini-transforms/src/document.ts +++ b/packages/graphql-mini-transforms/src/document.ts @@ -68,7 +68,11 @@ export function extractImports(rawSource: string) { return {imports: [...imports], source}; } -export function toSimpleDocument( +export function toSimpleDocument< + Data extends {}, + Variables extends {}, + DeepPartial extends {}, +>( document: DocumentNode, ): SimpleDocument { return { diff --git a/packages/jest-dom-mocks/src/connection.ts b/packages/jest-dom-mocks/src/connection.ts index 0a7583db00..8b17f72c2b 100644 --- a/packages/jest-dom-mocks/src/connection.ts +++ b/packages/jest-dom-mocks/src/connection.ts @@ -1,7 +1,8 @@ import {set} from './utilities'; export interface NavigatorWithConnection extends Navigator { - connection: Navigator['connection'] & { + connection: EventTarget & { + type: string; downlink: number; effectiveType: string; onchange: null | Function; diff --git a/packages/jest-dom-mocks/src/tests/connection.test.ts b/packages/jest-dom-mocks/src/tests/connection.test.ts index bd6f64fb4c..e2cb1a3902 100644 --- a/packages/jest-dom-mocks/src/tests/connection.test.ts +++ b/packages/jest-dom-mocks/src/tests/connection.test.ts @@ -1,4 +1,4 @@ -import {Connection} from '../connection'; +import {Connection, NavigatorWithConnection} from '../connection'; describe('Connection', () => { describe('mock', () => { @@ -29,7 +29,9 @@ describe('Connection', () => { connection.mock(mockValues); - expect(navigator.connection).toMatchObject(mockValues); + expect((navigator as NavigatorWithConnection).connection).toMatchObject( + mockValues, + ); }); }); diff --git a/packages/react-async/src/Prefetcher.tsx b/packages/react-async/src/Prefetcher.tsx index a2aae40b15..937a5042c3 100644 --- a/packages/react-async/src/Prefetcher.tsx +++ b/packages/react-async/src/Prefetcher.tsx @@ -12,7 +12,7 @@ interface State { } interface NavigatorWithConnection extends Navigator { - connection: Navigator['connection'] & {saveData: boolean}; + connection: {saveData: boolean}; } export const INTENTION_DELAY_MS = 150; diff --git a/packages/react-form-state/src/FormState.ts b/packages/react-form-state/src/FormState.ts index 893ee0fcea..c216298f7d 100644 --- a/packages/react-form-state/src/FormState.ts +++ b/packages/react-form-state/src/FormState.ts @@ -70,7 +70,10 @@ export default class FormState< static List = List; static Nested = Nested; - static getDerivedStateFromProps(newProps: Props, oldState: State) { + static getDerivedStateFromProps( + newProps: Props, + oldState: State, + ) { const { initialValues, onInitialValuesChange, @@ -409,7 +412,7 @@ export default class FormState< } } -function fieldsWithErrors( +function fieldsWithErrors( fields: Fields, errors: RemoteError[], ): Fields { @@ -436,7 +439,7 @@ function fieldsWithErrors( }); } -function reconcileFormState( +function reconcileFormState( values: Fields, oldState: State, externalErrors: RemoteError[] = [], @@ -467,7 +470,7 @@ function reconcileFormState( }; } -function createFormState( +function createFormState( values: Fields, externalErrors: RemoteError[] = [], ): State { diff --git a/packages/react-form-state/src/components/List.tsx b/packages/react-form-state/src/components/List.tsx index 916af6eb67..35305ebd7b 100644 --- a/packages/react-form-state/src/components/List.tsx +++ b/packages/react-form-state/src/components/List.tsx @@ -9,7 +9,7 @@ interface Props { getChildKey?(item: Fields): string; } -export default class List extends React.PureComponent< +export default class List extends React.PureComponent< Props, never > { diff --git a/packages/react-form-state/src/components/Nested.tsx b/packages/react-form-state/src/components/Nested.tsx index 4676fcaf69..aa305d2bdd 100644 --- a/packages/react-form-state/src/components/Nested.tsx +++ b/packages/react-form-state/src/components/Nested.tsx @@ -8,7 +8,7 @@ interface Props { children(fields: FieldDescriptors): React.ReactNode; } -export default class Nested extends React.PureComponent< +export default class Nested extends React.PureComponent< Props, never > { diff --git a/packages/react-form-state/src/utilities.ts b/packages/react-form-state/src/utilities.ts index 493fc86069..7c965261e1 100644 --- a/packages/react-form-state/src/utilities.ts +++ b/packages/react-form-state/src/utilities.ts @@ -4,7 +4,7 @@ import {FieldDescriptor} from './types'; export {isEqual}; -export function mapObject( +export function mapObject( input: Input, mapper: (value: any, key: string & keyof Input) => any, ) { diff --git a/packages/react-graphql/src/Prefetch.tsx b/packages/react-graphql/src/Prefetch.tsx index 88052ddb06..b85aafb709 100644 --- a/packages/react-graphql/src/Prefetch.tsx +++ b/packages/react-graphql/src/Prefetch.tsx @@ -8,7 +8,7 @@ export type Props = Pick< 'query' | 'variables' | 'onError' | 'onCompleted' | 'pollInterval' > & {ignoreCache?: boolean}; -export function Prefetch({ignoreCache, ...props}: Props) { +export function Prefetch({ignoreCache, ...props}: Props) { const fetchPolicy = ignoreCache ? 'network-only' : undefined; return ( diff --git a/packages/react-graphql/src/Query.tsx b/packages/react-graphql/src/Query.tsx index 4902605885..0fd02c6fe9 100644 --- a/packages/react-graphql/src/Query.tsx +++ b/packages/react-graphql/src/Query.tsx @@ -9,11 +9,10 @@ interface QueryComponentOptions extends QueryHookOptions { query: DocumentNode; } -export function Query({ - children, - query, - ...options -}: QueryComponentOptions) { +export function Query< + Data extends {} = any, + Variables extends OperationVariables = OperationVariables, +>({children, query, ...options}: QueryComponentOptions) { const opts = [options] as IfAllNullableKeys< Variables, [QueryHookOptions>?], diff --git a/packages/react-graphql/src/async/component.tsx b/packages/react-graphql/src/async/component.tsx index 742f1d815f..60a5e7a791 100644 --- a/packages/react-graphql/src/async/component.tsx +++ b/packages/react-graphql/src/async/component.tsx @@ -5,7 +5,11 @@ import {AsyncQueryComponentType, QueryProps, VariableOptions} from '../types'; import {Options, createAsyncQuery} from './query'; -export function createAsyncQueryComponent( +export function createAsyncQueryComponent< + Data extends {}, + Variables extends {}, + DeepPartial extends {}, +>( options: Options, ): AsyncQueryComponentType { const asyncQuery = createAsyncQuery(options); diff --git a/packages/react-graphql/src/async/query.ts b/packages/react-graphql/src/async/query.ts index 73624d584f..b9401cce02 100644 --- a/packages/react-graphql/src/async/query.ts +++ b/packages/react-graphql/src/async/query.ts @@ -8,7 +8,11 @@ import {AsyncDocumentNode, QueryProps, VariableOptions} from '../types'; export interface Options extends ResolverOptions> {} -export function createAsyncQuery({ +export function createAsyncQuery< + Data extends {}, + Variables extends {}, + DeepPartial extends {}, +>({ id, load, }: Options): AsyncDocumentNode< diff --git a/packages/react-graphql/src/hooks/graphql-document.ts b/packages/react-graphql/src/hooks/graphql-document.ts index 7bb4e4b3ab..512a79530e 100644 --- a/packages/react-graphql/src/hooks/graphql-document.ts +++ b/packages/react-graphql/src/hooks/graphql-document.ts @@ -7,9 +7,9 @@ import {useAsyncAsset} from '@shopify/react-async'; import {AsyncDocumentNode} from '../types'; export default function useGraphQLDocument< - Data = any, - Variables = OperationVariables, - DeepPartial = {}, + Data extends {} = any, + Variables extends OperationVariables = OperationVariables, + DeepPartial extends {} = {}, >( documentOrAsyncDocument: | DocumentNode diff --git a/packages/react-graphql/src/hooks/query.ts b/packages/react-graphql/src/hooks/query.ts index 22f5763838..0e8bd7790b 100644 --- a/packages/react-graphql/src/hooks/query.ts +++ b/packages/react-graphql/src/hooks/query.ts @@ -24,9 +24,9 @@ const { } = Object; export default function useQuery< - Data = any, - Variables = OperationVariables, - DeepPartial = {}, + Data extends {} = any, + Variables extends OperationVariables = OperationVariables, + DeepPartial extends {} = {}, >( queryOrAsyncQuery: | DocumentNode diff --git a/packages/react-idle/package.json b/packages/react-idle/package.json index bf42dc7a33..eaf9d24f15 100644 --- a/packages/react-idle/package.json +++ b/packages/react-idle/package.json @@ -24,8 +24,7 @@ "node": "^14.17.0 || >=16.0.0" }, "dependencies": { - "@shopify/async": "^4.0.1", - "@shopify/useful-types": "^5.1.1" + "@shopify/async": "^4.0.1" }, "peerDependencies": { "react": ">=16.8.0 <19.0.0" diff --git a/packages/react-idle/src/hooks.ts b/packages/react-idle/src/hooks.ts index e7a892339a..44a77ab608 100644 --- a/packages/react-idle/src/hooks.ts +++ b/packages/react-idle/src/hooks.ts @@ -1,11 +1,6 @@ import {useEffect, useRef} from 'react'; -import {ExtendedWindow} from '@shopify/useful-types'; -import { - RequestIdleCallbackHandle, - WindowWithRequestIdleCallback, - UnsupportedBehavior, -} from './types'; +import {RequestIdleCallbackHandle, UnsupportedBehavior} from './types'; export function useIdleCallback( callback: () => void, @@ -15,11 +10,9 @@ export function useIdleCallback( useEffect(() => { if ('requestIdleCallback' in window) { - handle.current = ( - window as ExtendedWindow - ).requestIdleCallback(() => callback()); + handle.current = window.requestIdleCallback(() => callback()); } else if (unsupportedBehavior === UnsupportedBehavior.AnimationFrame) { - handle.current = window.requestAnimationFrame(() => { + handle.current = (window as Window).requestAnimationFrame(() => { callback(); }); } else { @@ -35,11 +28,9 @@ export function useIdleCallback( } if ('cancelIdleCallback' in window) { - ( - window as ExtendedWindow - ).cancelIdleCallback(currentHandle); + window.cancelIdleCallback(currentHandle); } else if (unsupportedBehavior === UnsupportedBehavior.AnimationFrame) { - window.cancelAnimationFrame(currentHandle); + (window as Window).cancelAnimationFrame(currentHandle); } }; }, [callback, unsupportedBehavior]); diff --git a/packages/react-idle/src/types.ts b/packages/react-idle/src/types.ts index 477b0be7ef..13a4b63224 100644 --- a/packages/react-idle/src/types.ts +++ b/packages/react-idle/src/types.ts @@ -3,7 +3,6 @@ export type { RequestIdleCallbackOptions, RequestIdleCallbackDeadline, RequestIdleCallbackHandle, - WindowWithRequestIdleCallback, } from '@shopify/async'; export enum UnsupportedBehavior { diff --git a/packages/react-idle/tsconfig.json b/packages/react-idle/tsconfig.json index ed56b083f8..076935b169 100644 --- a/packages/react-idle/tsconfig.json +++ b/packages/react-idle/tsconfig.json @@ -13,7 +13,6 @@ "references": [ {"path": "../async"}, {"path": "../jest-dom-mocks"}, - {"path": "../react-testing"}, - {"path": "../useful-types"} + {"path": "../react-testing"} ] } diff --git a/packages/react-import-remote/package.json b/packages/react-import-remote/package.json index 490502d179..01e3c8779a 100644 --- a/packages/react-import-remote/package.json +++ b/packages/react-import-remote/package.json @@ -26,8 +26,7 @@ "@shopify/async": "^4.0.1", "@shopify/react-hooks": "^3.0.2", "@shopify/react-html": "^13.0.0", - "@shopify/react-intersection-observer": "^4.0.2", - "@shopify/useful-types": "^5.1.1" + "@shopify/react-intersection-observer": "^4.0.2" }, "peerDependencies": { "react": ">=16.8.0 <19.0.0" diff --git a/packages/react-import-remote/src/hooks.ts b/packages/react-import-remote/src/hooks.ts index 14a7ac8558..27050ed18e 100644 --- a/packages/react-import-remote/src/hooks.ts +++ b/packages/react-import-remote/src/hooks.ts @@ -1,11 +1,6 @@ import React from 'react'; -import {ExtendedWindow} from '@shopify/useful-types'; import {useIntersection} from '@shopify/react-intersection-observer'; -import { - DeferTiming, - WindowWithRequestIdleCallback, - RequestIdleCallbackHandle, -} from '@shopify/async'; +import {DeferTiming, RequestIdleCallbackHandle} from '@shopify/async'; import {useMountedRef} from '@shopify/react-hooks'; import load from './load'; @@ -98,9 +93,7 @@ export function useImportRemote( React.useEffect(() => { if (defer === DeferTiming.Idle) { if ('requestIdleCallback' in window) { - idleCallbackHandle.current = ( - window as ExtendedWindow - ).requestIdleCallback(loadRemote); + idleCallbackHandle.current = window.requestIdleCallback(loadRemote); } else { loadRemote(); } diff --git a/packages/react-import-remote/src/load.ts b/packages/react-import-remote/src/load.ts index 6dc6725bdf..6f31d8ea48 100644 --- a/packages/react-import-remote/src/load.ts +++ b/packages/react-import-remote/src/load.ts @@ -1,7 +1,7 @@ -import {ExtendedWindow} from '@shopify/useful-types'; - const cache = new Map>(); +type ExtendedWindow = Window & typeof globalThis & T; + export default function load< Imported = any, CustomWindow extends Window = Window, diff --git a/packages/react-import-remote/tsconfig.json b/packages/react-import-remote/tsconfig.json index ecc462018b..e921dd423f 100644 --- a/packages/react-import-remote/tsconfig.json +++ b/packages/react-import-remote/tsconfig.json @@ -14,7 +14,6 @@ {"path": "../async"}, {"path": "../react-hooks"}, {"path": "../react-html"}, - {"path": "../react-intersection-observer"}, - {"path": "../useful-types"} + {"path": "../react-intersection-observer"} ] } diff --git a/packages/react-server/src/tests/utilities.ts b/packages/react-server/src/tests/utilities.ts index 0406fe3c65..dcf1c0a404 100644 --- a/packages/react-server/src/tests/utilities.ts +++ b/packages/react-server/src/tests/utilities.ts @@ -14,7 +14,7 @@ export class TestRack { } async mount( - mountFunction: ({port: number, ip: string}) => Server, + mountFunction: ({port, ip}: {port: number; ip: string}) => Server, options: RequestInit = {}, ) { const port = await getPort(); diff --git a/packages/react-testing/src/toReactString.ts b/packages/react-testing/src/toReactString.ts index ed0c6300e4..984e3f9b31 100644 --- a/packages/react-testing/src/toReactString.ts +++ b/packages/react-testing/src/toReactString.ts @@ -2,7 +2,7 @@ import {stringify} from 'jest-matcher-utils'; import {DebugOptions, Node} from './types'; -export function toReactString( +export function toReactString( node: Node, options: DebugOptions = {}, level = 0, @@ -16,7 +16,7 @@ export function toReactString( const name = nodeName(node); const indent = ' '.repeat(level); - const props = Object.keys(node.props) + const props = Object.keys(node.props as {}) // we always filter out children no matter what, but unless allProps option // is present we will also filter out insigificant props .filter((key) => diff --git a/packages/react-testing/src/types.ts b/packages/react-testing/src/types.ts index 5be51999e5..6cbf7bc09a 100644 --- a/packages/react-testing/src/types.ts +++ b/packages/react-testing/src/types.ts @@ -199,7 +199,7 @@ export type ReactInstance = export type Predicate = (node: Node) => boolean; -export interface Node { +export interface Node { readonly props: Props; readonly type: string | React.ComponentType | null; readonly isDOM: boolean; diff --git a/yarn.lock b/yarn.lock index 6207eeb5b2..f7b7908e42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14123,10 +14123,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.3.5, typescript@~4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@^4.3.5, typescript@~4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== ua-parser-js@^0.7.33: version "0.7.33"