diff --git a/examples/hooks/components/QueryRuleCustomData.tsx b/examples/hooks/components/QueryRuleCustomData.tsx index 0f1b0d6e06..8b56cbf036 100644 --- a/examples/hooks/components/QueryRuleCustomData.tsx +++ b/examples/hooks/components/QueryRuleCustomData.tsx @@ -3,7 +3,10 @@ import React from 'react'; import { useQueryRules, UseQueryRulesProps } from 'react-instantsearch-hooks'; import { cx } from '../cx'; -export type QueryRuleCustomDataProps = React.ComponentProps<'div'> & +export type QueryRuleCustomDataProps = Omit< + React.ComponentProps<'div'>, + 'children' +> & Partial> & { children: (options: { items: any[] }) => React.ReactNode; }; diff --git a/packages/react-instantsearch-core/src/core/createConnector.tsx b/packages/react-instantsearch-core/src/core/createConnector.tsx index 9f49c5e39e..0d75e122b6 100644 --- a/packages/react-instantsearch-core/src/core/createConnector.tsx +++ b/packages/react-instantsearch-core/src/core/createConnector.tsx @@ -1,4 +1,4 @@ -import type { ReactType } from 'react'; +import type { ElementType } from 'react'; import React, { Component } from 'react'; import isEqual from 'react-fast-compare'; import { shallowEqual, getDisplayName, removeEmptyKey } from './utils'; @@ -86,7 +86,7 @@ export function createConnectorWithoutContext( typeof connectorDesc.transitionState === 'function'; return ( - Composed: ReactType, + Composed: ElementType, additionalWidgetProperties: AdditionalWidgetProperties = {} ) => { class Connector extends Component { @@ -363,7 +363,7 @@ export function createConnectorWithoutContext( const createConnectorWithContext = (connectorDesc: ConnectorDescription) => ( - Composed: ReactType, + Composed: ElementType, additionalWidgetProperties?: AdditionalWidgetProperties ) => { const Connector = createConnectorWithoutContext(connectorDesc)( diff --git a/packages/react-instantsearch-core/src/widgets/DynamicWidgets.tsx b/packages/react-instantsearch-core/src/widgets/DynamicWidgets.tsx index 84ae90234f..8da9900d7f 100644 --- a/packages/react-instantsearch-core/src/widgets/DynamicWidgets.tsx +++ b/packages/react-instantsearch-core/src/widgets/DynamicWidgets.tsx @@ -1,21 +1,25 @@ -import type { ReactChild, ComponentType, ReactNode } from 'react'; +import type { ComponentType, ReactElement, ReactNode } from 'react'; import React, { Fragment } from 'react'; import { getDisplayName } from '../core/utils'; import connectDynamicWidgets from '../connectors/connectDynamicWidgets'; -function getAttribute(component: ReactChild): string | undefined { - if (typeof component !== 'object') { +function isReactElement(element: any): element is ReactElement { + return typeof element === 'object' && element.props; +} + +function getAttribute(element: ReactNode): string | undefined { + if (!isReactElement(element)) { return undefined; } - if (component.props.attribute) { - return component.props.attribute; + if (element.props.attribute) { + return element.props.attribute; } - if (Array.isArray(component.props.attributes)) { - return component.props.attributes[0]; + if (Array.isArray(element.props.attributes)) { + return element.props.attributes[0]; } - if (component.props.children) { - return getAttribute(React.Children.only(component.props.children)); + if (element.props.children) { + return getAttribute(React.Children.only(element.props.children)); } return undefined; @@ -32,7 +36,7 @@ function DynamicWidgets({ attributesToRender, fallbackComponent: Fallback = () => null, }: DynamicWidgetsProps) { - const widgets: Map = new Map(); + const widgets: Map = new Map(); React.Children.forEach(children, (child) => { const attribute = getAttribute(child); diff --git a/packages/react-instantsearch-core/src/widgets/Index.tsx b/packages/react-instantsearch-core/src/widgets/Index.tsx index bbce51fc0f..22ad61716f 100644 --- a/packages/react-instantsearch-core/src/widgets/Index.tsx +++ b/packages/react-instantsearch-core/src/widgets/Index.tsx @@ -12,6 +12,7 @@ function getIndexContext(props: Props): IndexContext { type Props = { indexName: string; indexId: string; + children?: React.ReactNode; }; type InnerProps = Props & { contextValue: InstantSearchContext }; diff --git a/packages/react-instantsearch-core/src/widgets/InstantSearch.tsx b/packages/react-instantsearch-core/src/widgets/InstantSearch.tsx index ec3384eea2..89b93368fb 100644 --- a/packages/react-instantsearch-core/src/widgets/InstantSearch.tsx +++ b/packages/react-instantsearch-core/src/widgets/InstantSearch.tsx @@ -68,6 +68,7 @@ type Props = { }) => void; stalledSearchDelay?: number; resultsState?: ResultsState | { [indexId: string]: ResultsState }; + children?: React.ReactNode; }; type State = { diff --git a/packages/react-instantsearch-dom/src/components/Hits.tsx b/packages/react-instantsearch-dom/src/components/Hits.tsx index c0ff624d4f..241503e8e9 100644 --- a/packages/react-instantsearch-dom/src/components/Hits.tsx +++ b/packages/react-instantsearch-dom/src/components/Hits.tsx @@ -16,7 +16,7 @@ type Props = { className?: string; hitComponent?: | string - | React.ReactType + | React.ElementType | React.ExoticComponent; }; diff --git a/packages/react-instantsearch-hooks-server/src/getServerState.tsx b/packages/react-instantsearch-hooks-server/src/getServerState.tsx index f60dba592a..e6646710e9 100644 --- a/packages/react-instantsearch-hooks-server/src/getServerState.tsx +++ b/packages/react-instantsearch-hooks-server/src/getServerState.tsx @@ -8,7 +8,7 @@ import { import type { InitialResults, InstantSearch } from 'instantsearch.js'; import type { IndexWidget } from 'instantsearch.js/es/widgets/index/index'; import type { ReactNode } from 'react'; -import type { renderToString as RenderToString } from 'react-dom/server'; +import type { renderToString as reactRenderToString } from 'react-dom/server'; import type { InstantSearchServerContextApi, InstantSearchServerState, @@ -73,7 +73,7 @@ export function getServerState( type ExecuteArgs = { children: ReactNode; - renderToString: typeof RenderToString; + renderToString: typeof reactRenderToString; notifyServer: InstantSearchServerContextApi['notifyServer']; searchRef: SearchRef; }; @@ -190,7 +190,7 @@ function importRenderToString() { return Promise.all(modules.map((mod) => import(mod).catch(() => {}))).then( (imports: unknown[]) => { const ReactDOMServer = imports.find( - (mod): mod is { renderToString: typeof RenderToString } => + (mod): mod is { renderToString: typeof reactRenderToString } => mod !== undefined ); diff --git a/packages/react-instantsearch-hooks-web/src/ui/InternalHighlight.tsx b/packages/react-instantsearch-hooks-web/src/ui/InternalHighlight.tsx index 404be4570a..1f7359f11e 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/InternalHighlight.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/InternalHighlight.tsx @@ -5,8 +5,8 @@ import { cx } from './lib/cx'; type HighlightPartProps = { children: React.ReactNode; classNames: InternalHighlightClassNames; - highlightedTagName: React.ReactType; - nonHighlightedTagName: React.ReactType; + highlightedTagName: React.ElementType; + nonHighlightedTagName: React.ElementType; isHighlighted: boolean; }; @@ -56,8 +56,8 @@ export type InternalHighlightClassNames = { export type InternalHighlightProps = React.HTMLAttributes & { classNames: InternalHighlightClassNames; - highlightedTagName?: React.ReactType; - nonHighlightedTagName?: React.ReactType; + highlightedTagName?: React.ElementType; + nonHighlightedTagName?: React.ElementType; separator?: React.ReactNode; parts: HighlightedPart[][]; }; diff --git a/packages/react-instantsearch-hooks/src/components/DynamicWidgets.tsx b/packages/react-instantsearch-hooks/src/components/DynamicWidgets.tsx index 5591dd911e..c53574c201 100644 --- a/packages/react-instantsearch-hooks/src/components/DynamicWidgets.tsx +++ b/packages/react-instantsearch-hooks/src/components/DynamicWidgets.tsx @@ -4,7 +4,7 @@ import { useDynamicWidgets } from '../connectors/useDynamicWidgets'; import { invariant } from '../lib/invariant'; import type { DynamicWidgetsConnectorParams } from 'instantsearch.js/es/connectors/dynamic-widgets/connectDynamicWidgets'; -import type { ReactChild, ComponentType, ReactNode } from 'react'; +import type { ReactElement, ComponentType, ReactNode } from 'react'; function FallbackComponent() { return null; @@ -32,7 +32,7 @@ export function DynamicWidgets({ const { attributesToRender } = useDynamicWidgets(props, { $$widgetType: 'ais.dynamicWidgets', }); - const widgets: Map = new Map(); + const widgets: Map = new Map(); React.Children.forEach(children, (child) => { const attribute = getWidgetAttribute(child); @@ -56,22 +56,26 @@ export function DynamicWidgets({ ); } -function getWidgetAttribute(component: ReactChild): string | undefined { - if (typeof component !== 'object') { +function isReactElement(element: any): element is ReactElement { + return typeof element === 'object' && element.props; +} + +function getWidgetAttribute(element: ReactNode): string | undefined { + if (!isReactElement(element)) { return undefined; } - if (component.props.attribute) { - return component.props.attribute; + if (element.props.attribute) { + return element.props.attribute; } - if (Array.isArray(component.props.attributes)) { - return component.props.attributes[0]; + if (Array.isArray(element.props.attributes)) { + return element.props.attributes[0]; } - if (component.props.children) { + if (element.props.children) { invariant( - React.Children.count(component.props.children) === 1, + React.Children.count(element.props.children) === 1, ` only supports a single component in nested components. Make sure to not render multiple children in a parent component. Example of an unsupported scenario: @@ -87,7 +91,7 @@ Example of an unsupported scenario: ` ); - return getWidgetAttribute(React.Children.only(component.props.children)); + return getWidgetAttribute(React.Children.only(element.props.children)); } return undefined; diff --git a/packages/react-instantsearch-hooks/src/lib/useForceUpdate.ts b/packages/react-instantsearch-hooks/src/lib/useForceUpdate.ts index 46a9565409..dc9cf6d79d 100644 --- a/packages/react-instantsearch-hooks/src/lib/useForceUpdate.ts +++ b/packages/react-instantsearch-hooks/src/lib/useForceUpdate.ts @@ -5,7 +5,7 @@ import { useReducer } from 'react'; * @link https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate */ export function useForceUpdate() { - const [, forceUpdate] = useReducer((x) => x + 1, 0); + const [, forceUpdate] = useReducer((x) => x + 1, 0); return forceUpdate; } diff --git a/tsconfig.json b/tsconfig.json index b5783fd7b0..11435c51d6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,13 @@ "allowSyntheticDefaultImports": true, "skipLibCheck": true }, - "exclude": ["examples/hooks-next/next-env.d.ts"] + "exclude": [ + "examples/hooks-next/next-env.d.ts", + // @TODO: we need to re-enable type checking in the Next.js example + // once it's migrated to React 18. + "examples/hooks-next", + // @TODO: we need to re-enable type checking in the React Native example + // when the RN types work with React 18. + "examples/hooks-react-native" + ] }