1

I'm trying to build a convenience function that allows me to pass a react component and have it render the component with all of the options and route specifications that I want that I pass it wrapped around a <BrowserRouter> for unit testing.

The problem is when I try to pass Wrapper function into react testing library's render() (see rtlRender() below), I'm getting an error from TypeScript stating the following

No overload matches this call. Overload 1 of 2, '(ui: ReactElement<any, string | JSXElementConstructor>, options: RenderOptions<Queries, HTMLElement>): RenderResult<...>', gave the following error. Type '({ children }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}> | undefined'. Type '({ children }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'. Types of parameters '__0' and 'props' are incompatible. Type '{ children?: ReactNode; }' is not assignable to type 'Props'. Types of property 'children' are incompatible. Type 'ReactNode' is not assignable to type 'ReactChildren'. Type 'undefined' is not assignable to type 'ReactChildren'. Overload 2 of 2, '(ui: ReactElement<any, string | JSXElementConstructor>, options?: Omit<RenderOptions<typeof import("/Users/myuser/dev/myapp/container/node_modules/@testing-library/dom/types/queries"), HTMLElement>, "queries"> | undefined): RenderResult<...>', gave the following error. Type '({ children }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}> | undefined'. Type '({ children }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.ts(2769) index.d.ts(41, 3): The expected type comes from property 'wrapper' which is declared here on type 'RenderOptions<Queries, HTMLElement>' (property) RenderOptions<Queries, HTMLElement>.wrapper?: React.ComponentType<{}> | undefined

Here's my code

import React from 'react';
import { Queries, render as rtlRender } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { RenderResult } from '@testing-library/react/types';

/* Type Definitions */
type Route = {
  state: Record<string, unknown>;
  title: string;
  location: string;
};
type Props = {
  children: React.ReactChildren;
};

/* Helper Functions */
export function render(ui: JSX.Element, options: { route: Route }): RenderResult<Queries, HTMLElement> {
  const { route } = options;
  window.history.pushState(route?.state ?? null, route?.title ?? 'Root', route?.location ?? '/');

  const Wrapper = ({ children }: Props) => {
    return <BrowserRouter>{children}</BrowserRouter>;
  };

  return rtlRender(ui, { wrapper: Wrapper, ...options });
}

From what I understand, it's telling me that whatever Wrapper() returns doesn't fit the description of a React class component or functional component. This seems weird to me because you can tell from the code, Wrapper() should return a functional component.

I tried to assign Wrapper() to type React.FC<Props>, but TypeScript states that React.FC<Props> is not of type React.ComponentType which made me even more confused.

I'm still new to TypeScript so throwing in all these React types is confusing me even more. I'm hoping for some guidance on this.

1 Answer 1

2

React.FC type is alias of React.FunctionComponent type. The props of React.FunctionComponent is PropsWithChildren type which has children?: ReactNode property. You don't need to create your own Props type.

The options parameter type of render function is:

export interface RenderOptions<Q extends Queries = typeof queries> {
  container?: Element
  baseElement?: Element
  hydrate?: boolean
  queries?: Q
  wrapper?: React.ComponentType
}

There is no route property, I think it's for the custom render function. But you need to use Intersection Types so that you can pass the options to render function of RTL.

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

type PropsWithChildren<P> = P & { children?: ReactNode };

The default generic parameter type for RenderResult is typeof queries, NOT Queries. This is the Queries interface:

export interface Queries {
    [T: string]: Query;
}

It's totally different with @testing-library/dom/types/queries.d.ts.

import React from 'react';
import { queries, render as rtlRender, RenderOptions } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { RenderResult } from '@testing-library/react/types';

/* Type Definitions */
type Route = {
  state: Record<string, unknown>;
  title: string;
  location: string;
};

/* Helper Functions */
export function render(
  ui: React.ReactElement,
  options: { route: Route } & Omit<RenderOptions, 'queries'>
): RenderResult<typeof queries> {
  const { route, ...rest } = options;
  window.history.pushState(route?.state ?? null, route?.title ?? 'Root', route?.location ?? '/');

  const Wrapper: React.FC = ({ children }) => {
    return <BrowserRouter>{children}</BrowserRouter>;
  };

  return rtlRender(ui, { wrapper: Wrapper, ...rest });
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.