react-native-onboarding-swiper icon indicating copy to clipboard operation
react-native-onboarding-swiper copied to clipboard

'Onboarding' cannot be used as a JSX component

Open juanmigdr opened this issue 11 months ago • 4 comments

Hello,

Thank you so much for this amazing library!

After updating to the latest Expo version, I am encountering the following error:

screens/OnboardingScreen.tsx:34:8 - error TS2786: 'Onboarding' cannot be used as a JSX component.
  Its type 'typeof Onboarding' is not a valid JSX element type.
    Types of construct signatures are incompatible.
      Type 'new (props: Props) => Onboarding' is not assignable to type 'new (props: any, deprecatedLegacyContext?: any) => Component<any, any, any>'.
        Property 'refs' is missing in type 'Onboarding' but required in type 'Component<any, any, any>'.

34       <Onboarding
          ~~~~~~~~~~

  node_modules/@types/react/index.d.ts:1040:9
    1040         refs: {
                 ~~~~
    'refs' is declared here.


Found 1 error in screens/OnboardingScreen.tsx:34

I’ve investigated and found that if I update @types/react to the latest version, the error seems to go away. However, the problem is that the latest supported version of @types/react for Expo is 18.3.12, and when running it, expo-doctor fails when using the latest version of React types.

I’m hoping for a solution that resolves this issue without having to upgrade @types/react cause it would mean expo-doctor will fail on my project. Any help would be greatly appreciated!

Thank you in advance.

juanmigdr avatar Jan 15 '25 17:01 juanmigdr

I am also facing the same issue. Any solutions found yet?? I currently cannot view my onboarding screens.

cassimHamisi avatar Feb 24 '25 12:02 cassimHamisi

I have found a temporary fix. Typecasting the onboarding screen types and then prioritizing your types over the intalled package types. Create a root folder of your project called types and add a file named: react-native-onboarding-swiper.d.ts with the following as content:

// types/react-native-onboarding-swiper.d.ts

declare module "react-native-onboarding-swiper" { import * as React from "react";

export interface OnboardingProps { controlStatusBar?: boolean; pages: Array<{ backgroundColor?: string; image?: React.ReactNode; title?: React.ReactNode | string; subtitle?: React.ReactNode | string; titleStyles?: any; subTitleStyles?: any; }>; bottomBarColor?: string; bottomBarHeight?: number; onSkip?: () => void; containerStyles?: any; onDone?: () => void; pageIndexCallback?: (index: number) => void; showSkip?: boolean; }

// Instead of declaring a class, declare Onboarding as a component type. const Onboarding: React.ComponentType<OnboardingProps>; export default Onboarding; }

Remember to priorize your types in the tsconfig.json file in the typeRoots field: { "extends": "expo/tsconfig.base", "compilerOptions": { "jsx": "react-native", "strict": true, "typeRoots": ["./types", "./node_modules/@types"] } }

Restart you vs code and server and everything should work without the typescript errors

cassimHamisi avatar Mar 07 '25 09:03 cassimHamisi

Thanks @cassimHamisi, your solution partly solved my implementation but it was missing some props. Here is the final types/react-native-onboarding-swiper.d.ts file:

declare module "react-native-onboarding-swiper" {
  import { Component, FC, JSX } from "react";
  import {
    FlatList,
    FlatListProps,
    StyleProp,
    TextStyle,
    ViewStyle,
  } from "react-native";

  export interface SkipButtonProps {
    skipLabel: string | JSX.Element;
    isLight: boolean;
    allowFontScaling: boolean;
    onPress: () => any;
  }

  export interface NextButtonProps {
    nextLabel: string | JSX.Element;
    isLight: boolean;
    allowFontScaling: boolean;
    onPress: () => any;
  }

  export interface DoneButtonProps {
    isLight: boolean;
    allowFontScaling: boolean;
    onPress: () => any;
  }

  export interface DotProps {
    selected: boolean;
    isLight: boolean;
  }

  export interface Page {
    /**
     * A background color. The color of the font and dots adapts to the background color.
     */
    backgroundColor: string;

    /**
     * A component (e.g. <Image />) to display at the top of the page.
     */
    image: JSX.Element;

    /**
     * A string OR a React-Native component.
     */
    title: string | JSX.Element;

    /**
     * A string OR a React-Native component.
     */
    subtitle: string | JSX.Element;

    // INDIVIDUAL PAGE STYLES

    /**
     * Modify styles of a specific page's title.
     */
    titleStyles?: StyleProp<TextStyle> | undefined;

    /**
     * Modify styles of a specific page's subtitle.
     */
    subTitleStyles?: StyleProp<TextStyle> | undefined;
  }

  export interface Props {
    /**
     * An array of pages in the following shape.
     */
    pages: Page[];

    // BUTTONS

    /**
     * A string OR a React-Native component for the Next label.
     * @default "Next"
     */
    nextLabel?: string | JSX.Element | undefined;

    /**
     * A bool flag indicating whether the Next button is visible.
     * @default true
     */
    showNext?: boolean | undefined;

    /**
     * A string OR a React-Native component for the Skip label.
     * @default "Skip"
     */
    skipLabel?: string | JSX.Element | undefined;

    /**
     * A bool flag indicating whether the Skip button is visible.
     * @default true
     */
    showSkip?: boolean | undefined;

    /**
     * A callback that is fired if the Onboarding is skipped.
     */
    onSkip?: (() => any) | undefined;

    /**
     * When pressing skip, go to that page (e.g. skipToPage={2}). If this prop is provided, ignores onSkip.
     */
    skipToPage?: number | undefined;

    /**
     * A callback that is fired after the Onboarding is completed.
     */
    onDone?: (() => any) | undefined;

    /**
     * A bool flag indicating whether the Done checkmark button is visible.
     * @default true
     */
    showDone?: boolean | undefined;

    // GENERAL

    /**
     * A number for the height of the bottom bar.
     * @default 60
     */
    bottomBarHeight?: number | undefined;

    /**
     * BackgroundColor of the bottom bar.
     * @default "transparent"
     */
    bottomBarColor?: string | undefined;

    /**
     * A bool flag indicating whether the bottom bar should be highlighted.
     * @default true
     */
    bottomBarHighlight?: boolean | undefined;

    /**
     * A bool flag indicating whether the status bar should change with the background color.
     * @default true
     */
    controlStatusBar?: boolean | undefined;

    /**
     * Whether to show the bottom pagination bar.
     * @default true
     */
    showPagination?: boolean | undefined;

    /**
     * Additional props for the FlatList which holds all the pages.
     */
    flatlistProps?: FlatListProps<Page> | undefined;

    /**
     * The duration in milliseconds for the animation of the background color for the page transition.
     * @default 500
     */
    transitionAnimationDuration?: number | undefined;

    /**
     * Font scaling can cause troubles with high-resolution screens. You may want to disable it.
     * @default true
     */
    allowFontScaling?: boolean | undefined;

    /**
     * A function that receives the page index as a parameter on page change. Example Usage.
     */
    pageIndexCallback?: ((pageIndex: number) => any) | undefined;

    // DEFAULT PAGE STYLES

    /**
     * Override the default container styles.
     */
    containerStyles?: StyleProp<ViewStyle> | undefined;

    /**
     * Override the default image container styles e.g. the paddingBottom of 60.
     */
    imageContainerStyles?: StyleProp<ViewStyle> | undefined;

    /**
     * Override the default title styles.
     */
    titleStyles?: StyleProp<TextStyle> | undefined;

    /**
     * Override the default subtitle styles.
     */
    subTitleStyles?: StyleProp<TextStyle> | undefined;

    // CUSTOM COMPONENTS

    /**
     * Skip Button, gets skipLabel as prop.
     */
    SkipButtonComponent?: FC<SkipButtonProps> | undefined;

    /**
     * Next Button, gets nextLabel as prop.
     */
    NextButtonComponent?: FC<NextButtonProps> | undefined;

    /**
     * Done Button.
     */
    DoneButtonComponent?: FC<DoneButtonProps> | undefined;

    /**
     * Dot for the pagination, gets selected as prop to indicate the active page.
     */
    DotComponent?: FC<DotProps> | undefined;
  }

  export default class Onboarding extends Component<Props> {
    flatList?: FlatList;
    goNext: () => void;
  }
}

And as you said, do not forget to add the types folder path to your tsconfig.json:

"typeRoots": ["./types", "./node_modules/types"]

juanmigdr avatar May 26 '25 21:05 juanmigdr

Thanks @cassimHamisi and @juanmigdr , I ran into the same issue and the solution of creating a custom react-native-onboarding-swiper.d.ts file worked for me as well.

I placed the file in the root of the project and added it to tsconfig.json under include like this:

"include": [
  "**/*.ts",
  "**/*.tsx",
  ".expo/types/**/*.ts",
  "expo-env.d.ts",
  "./react-native-onboarding-swiper.d.ts"
]

After that, the error went away.

Hopefully this issue will be fixed soon so we can remove the custom .d.ts file.

One more tip: for better typing you can also import the Page type directly from the library:

import Onboarding, { type Page } from 'react-native-onboarding-swiper';

Wolf-Den1994 avatar Sep 16 '25 08:09 Wolf-Den1994