olefrank
olefrank

Reputation: 6810

react-toastr 3 with typescript

Trying to use react-toastr in my react/TS app. But the example provided (https://tomchentw.github.io/react-toastr/) does not work.

I import like this:

import { ToastContainer, ToastMessageAnimated } from "react-toastr";

And use like this:

class RegistrationSelectionPage extends React.Component<{}, {}> {

 public render() {
    let container: ToastContainer | null;

    return (
      <section className="RegistrationSelectionPage">
        <ToastContainer
          ref={ref => container = ref}
          className="toast-top-right"
          toastMessageFactory={ToastMessageAnimated}
        />
        <button onClick={() => container && container.warning('No connection', 'Error!')}>Click me</button>
...

I get this error:

Uncaught TypeError: Cannot call a class as a function
    at ./node_modules/babel-runtime/helpers/classCallCheck.js.exports.default 
...

Which function should I call on ToastMessageAnimated?

Please provide a complete working example, thanks :)

Upvotes: 0

Views: 1668

Answers (1)

alechill
alechill

Reputation: 4502

The @types are incorrect and look like they have allowed any for toastMessageFactory prop to get around a misunderstanding of the correct types, leading to all kinds of confusion

From the docs it expects a React factory function https://tomchentw.github.io/react-toastr/#toastmessage

The default is React.createFactory(ToastMessageAnimated) which is a factory function created from a Component class defined in the lib. This does serve your current purpose, but the current defs force you to specify it, and accepts any which defeats the point entirely idea, so lets look into fixing the defs

From the default value we can deduce the type of the toastMessageFactory should be React.Factory<ToastMessageProps> which is a factory for one of the component classes from the lib that has the basic ToastMessageProps

Looking up the props and the components in the source code (happily written with Flow so this becomes much easier) we can improve the @type/react-toastr defs as...

import { Component, ReactHTML, Factory, ReactNode } from 'react'

export interface ToastContainerProps {
  toastType?: IconClassNames
  id?: string
  preventDuplicates?: boolean
  toastMessageFactory?: Factory<ToastMessageProps>
  newestOnTop?: boolean
  onClick?: Function
}

export class ToastContainer extends Component<ToastContainerProps> {
    error: <P extends ToastMessageProps>(message: ReactNode, title: ReactNode, optionsOverride?: Partial<P>) => void
    info: <P extends ToastMessageProps>(message: ReactNode, title: ReactNode, optionsOverride?: Partial<P>) => void
    success: <P extends ToastMessageProps>(message: ReactNode, title: ReactNode, optionsOverride?: Partial<P>) => void
    warning: <P extends ToastMessageProps>(message: ReactNode, title: ReactNode, optionsOverride?: Partial<P>) => void
    clear: () => void
}

export interface IconClassNames {
    error: string
    info: string
    success: string
    warning: string
}

export interface ToastMessageProps {
    className?: string,
    type: string,
    iconClassNames?: IconClassNames,
    iconClassName?: string,
    closeButton?: boolean,
    onCloseClick?: Function,
    title?: any,
    titleClassName?: string,
    message?: any,
    messageClassName?: string
}

export interface ToastMessageAnimatedProps extends ToastMessageProps {
    className?: string,
    showAnimation?: string,
    hideAnimation?: string,
    timeOut?: number,
    extendedTimeOut?: number,
    tapToDismiss?: boolean,
    onRemove: Function,
    onClick?: Function,
    onCloseClick?: Function,
}

export interface ToastMessageQueryProps extends ToastMessageProps {
  style?: object,
  showMethod?: string,
  showDuration?: number,
  showEasing?: string,
  hideMethod?: string,
  hideDuration?: number,
  hideEasing?: string,
  timeOut?: number,
  extendedTimeOut?: number,
  tapToDismiss?: boolean,
  onRemove: Function,
  onClick?: Function,
  onCloseClick?: Function,
}

export class ToastMessageAnimated extends Component<ToastMessageAnimatedProps> {}
export class ToastMessageQuery extends Component<ToastMessageQueryProps> {}

You can now use these properly typed in your own component...

import { 
  ToastContainer, 
  ToastMessageAnimated,
  ToastMessageAnimatedProps // import this too for stricter typing of factory
} from "react-toastr"

//... all exactly same as your example, except...

<ToastContainer ref={ref => container = ref}
                toastMessageFactory={React.createFactory<ToastMessageAnimatedProps>(ToastMessageAnimated)}
                />
<button onClick={() => container && container.warning<ToastMessageAnimatedProps>('No connection', 'Error!', {className: 'toast-top-right'})}>Click me</button>

Here we are also strictly typing the options for the warning, and setting the className in it (this was set incorrectly as a container prop, so would not have been applied)

This also now allows you to pass JSX components as message and title too

container.warning<ToastMessageAnimatedProps>(<div>No connection</div>, <h1>Error!</h1>, {className: 'toast-top-right'})

Upvotes: 1

Related Questions