Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
fix(types): support React 18 types (#3481)
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour committed Jun 14, 2022
1 parent 1e12647 commit 74cf8cb
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 35 deletions.
5 changes: 4 additions & 1 deletion examples/hooks/components/QueryRuleCustomData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pick<UseQueryRulesProps, 'transformItems'>> & {
children: (options: { items: any[] }) => React.ReactNode;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -86,7 +86,7 @@ export function createConnectorWithoutContext(
typeof connectorDesc.transitionState === 'function';

return (
Composed: ReactType,
Composed: ElementType,
additionalWidgetProperties: AdditionalWidgetProperties = {}
) => {
class Connector extends Component<ConnectorProps, ConnectorState> {
Expand Down Expand Up @@ -363,7 +363,7 @@ export function createConnectorWithoutContext(
const createConnectorWithContext =
(connectorDesc: ConnectorDescription) =>
(
Composed: ReactType,
Composed: ElementType,
additionalWidgetProperties?: AdditionalWidgetProperties
) => {
const Connector = createConnectorWithoutContext(connectorDesc)(
Expand Down
24 changes: 14 additions & 10 deletions packages/react-instantsearch-core/src/widgets/DynamicWidgets.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -32,7 +36,7 @@ function DynamicWidgets({
attributesToRender,
fallbackComponent: Fallback = () => null,
}: DynamicWidgetsProps) {
const widgets: Map<string, ReactChild> = new Map();
const widgets: Map<string, ReactNode> = new Map();

React.Children.forEach(children, (child) => {
const attribute = getAttribute(child);
Expand Down
1 change: 1 addition & 0 deletions packages/react-instantsearch-core/src/widgets/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function getIndexContext(props: Props): IndexContext {
type Props = {
indexName: string;
indexId: string;
children?: React.ReactNode;
};

type InnerProps = Props & { contextValue: InstantSearchContext };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Props = {
}) => void;
stalledSearchDelay?: number;
resultsState?: ResultsState | { [indexId: string]: ResultsState };
children?: React.ReactNode;
};

type State = {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-instantsearch-dom/src/components/Hits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Props = {
className?: string;
hitComponent?:
| string
| React.ReactType<HitProps>
| React.ElementType<HitProps>
| React.ExoticComponent<HitProps>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -73,7 +73,7 @@ export function getServerState(

type ExecuteArgs = {
children: ReactNode;
renderToString: typeof RenderToString;
renderToString: typeof reactRenderToString;
notifyServer: InstantSearchServerContextApi['notifyServer'];
searchRef: SearchRef;
};
Expand Down Expand Up @@ -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
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -56,8 +56,8 @@ export type InternalHighlightClassNames = {

export type InternalHighlightProps = React.HTMLAttributes<HTMLSpanElement> & {
classNames: InternalHighlightClassNames;
highlightedTagName?: React.ReactType;
nonHighlightedTagName?: React.ReactType;
highlightedTagName?: React.ElementType;
nonHighlightedTagName?: React.ElementType;
separator?: React.ReactNode;
parts: HighlightedPart[][];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -32,7 +32,7 @@ export function DynamicWidgets({
const { attributesToRender } = useDynamicWidgets(props, {
$$widgetType: 'ais.dynamicWidgets',
});
const widgets: Map<string, ReactChild> = new Map();
const widgets: Map<string, ReactNode> = new Map();

React.Children.forEach(children, (child) => {
const attribute = getWidgetAttribute(child);
Expand All @@ -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,
`<DynamicWidgets> only supports a single component in nested components. Make sure to not render multiple children in a parent component.
Example of an unsupported scenario:
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, void>((x) => x + 1, 0);
const [, forceUpdate] = useReducer((x) => x + 1, 0);

return forceUpdate;
}
10 changes: 9 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}

0 comments on commit 74cf8cb

Please sign in to comment.