Notistack: Typescript compile errors

Created on 5 Feb 2019  Â·  20Comments  Â·  Source: iamhosseindhv/notistack

export default withSnackbar(Chat)

throws this error when compiled:
Type error: Argument of type 'typeof Chat' is not assignable to parameter of type 'ComponentType<InjectedNotistackProps>'.

| Tech | Version |
|--------------|---------|
| Notistack | v0.4.2 |
| React |v16.7.0-alpha.2 |
|@material-ui/core |v3.7.1 |
|typescript | v3.2.2|

needs more info

Most helpful comment

Be aware InjectedSnackbarProps is going to be renamed to withSnackbarProps in the next release.

All 20 comments

@adventmedia Would you test this with notistack v4.0.1 and see if it's the new version that is causing the problem or not.

I assume you meant v0.4.1… which I tried; same error.
I added // @ts-ignore above the line in question, it allows the app to compile and so far appears to run as expected. I don't like defeating typescript - defeats the purpose - but hey, if it works…

@adventmedia Yes that was a typo. It's weird that it's happening even with v0.4.1 as it's been out for about 2 months and no one reported an issue regarding this error. I might be able to help/debug if you share Chat component's code.
You can also have a look into typedef file: index.d.ts

unfortunately not at liberty to share the code. Typescript version is 3.2.2 in case that's relevant.

Added typescript version to the issue. Will leave this open for a while in case other people wanted to report the same issue.

I ran into the same issue and am currently using // @ts-ignore as the temporary fix.

Here is the tslint error and hint:

Argument of type 'typeof EventTable' is not assignable to parameter of type 'ComponentType<InjectedNotistackProps>'.
  Type 'typeof EventTable' is not assignable to type 'ComponentClass<InjectedNotistackProps, any>'.
    Types of parameters 'props' and 'props' are incompatible.
      Type 'InjectedNotistackProps' is missing the following properties from type 'EventTableProps': events, locations, teamGroups, timeSlots, and 3 more.ts(2345)

Hi @iamhosseindhv . Faced the same problem but when using JS.
As far as I understand the problem: withSnackbar element. I use Apollo and Next.js and wrap all classes in the app like:

class AppWithSnackBar extends React.Component {
    render() {
        const {children} = this.props;

        const childrenWithProps = React.Children.map(children, child =>
            React.cloneElement(child, {
                onPresentSnackbar: this.props.onPresentSnackbar,
                enqueueSnackbar: this.props.enqueueSnackbar
            })
        );

        return <React.Fragment>{childrenWithProps}</React.Fragment>
    }
}

export default withSnackbar(AppWithSnackBar);

Thus, in a class where you need to call a snackbar, you need to add

 props.enqueueSnackbar("You successfully logged in", {
                        variant: "success",
                        action: <Button size="small" color="inherit"><Clear color="inherit"/></Button>
                    });

But when I call a mutation to get a session and user, it causes an endless loop.

Version 0.4.1 works perfect.

What have you changed in the new version? Perhaps withSnackbar logic has changed?

UPDATE.

As far as I saw you add closeSnackbar property to withSnackbar component in 0.4.2. I don't understand why I need a method closeSnackbar. Could you tell something about this situation?

@AlexanderVishnevsky closeSnackbar, as the name suggests, allows you to dismiss a snackbar programmatically. See doc website example for more info.

I don't have enough info to find the issue in the problem you described. Maybe you (or other people facing this issue) could reproduce the error and provide a sandbox example?

Dealing with this same error AND I'm also using functional components instead of class-based, as mentioned here: https://github.com/iamhosseindhv/notistack/issues/29

Here's my TS code, which includes how I've temporarily solved the "use enqueueSnackbar when props isn't available" issue. Not thrilled with the solution but it'll get the job done for now.

import React from 'react';
import { createStyles, withStyles } from '@material-ui/core/styles';
import { withSnackbar } from 'notistack';

import CssBaseline from '@material-ui/core/CssBaseline';

import Header from './Header';
import Sidebar from './Sidebar';

interface Theme {
  mixins: {
    toolbar: any;
  };
  spacing: {
    unit: number;
  };
}

const styles = (theme: Theme) =>
  createStyles({
    root: {
      display: 'flex',
    },
    content: {
      flexGrow: 1,
      padding: theme.spacing.unit * 3,
    },
    toolbar: theme.mixins.toolbar,
  });

interface Props {
  classes: {
    root: string;
    toolbar: string;
    content: string;
  };
  enqueueSnackbar: any;
}

const fetchFakeData = (enqueueSnackbar: (arg0: string) => boolean) => {
  enqueueSnackbar('Success?');
};

const AppLayoutContainer = (props: Props) => {
  const { classes, enqueueSnackbar } = props;
  return (
    <div className={classes.root}>
      <CssBaseline />
      <Header />
      <Sidebar />
      <div className={classes.content}>
        <div className={classes.toolbar} />
        <a onClick={() => fetchFakeData(enqueueSnackbar)}>Test</a>
      </div>
    </div>
  );
};

export default withStyles(styles)(withSnackbar(AppLayoutContainer));

If I change this line:

const AppLayoutContainer = (props: Props) => {

to

const AppLayoutContainer = (props: any) => {

then everything works ... but I'd really like to actually use a TS interface rather than relying on any. If you have any insight, I'd appreciate it! Let me know if there's more info I can provide that might help.

Thanks @cwbuecheler. @AlexanderVishnevsky mentioned that his issue is only appearing in v0.4.2 and v0.4.1 works fine. would you try keeping Props and try with version v0.4.1?

@iamhosseindhv - Hmmm. Nope, I'm getting the error with v0.4.1 as well.

Argument of type '(props: Props) => Element' is not assignable to parameter of type 'ComponentType<InjectedNotistackProps>'.
  Type '(props: Props) => Element' is not assignable to type 'FunctionComponent<InjectedNotistackProps>'.
    Types of parameters 'props' and 'props' are incompatible.
      Property 'classes' is missing in type 'InjectedNotistackProps & { children?: ReactNode; }' but required in type 'Props'.ts(2345)
AppLayout.tsx(36, 3): 'classes' is declared here.

It is _very_ possible that I'm not building my Interface correctly as I'm fairly new to TypeScript. :)

Alright then we'll continue keeping the issue open for more info or ideally some codesandbox.

Thanks. If I have a chance this weekend I'll try to get something up in a sandbox. Swamped today!

I believe I found a solution for this. Essentially, you have to import the InjectedNotistackProps interface and combine it with your own Props interface in order for the HOC's to behave nicely. Here's some (slightly obfuscated) working code:

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { Typography } from '@material-ui/core';
import { withSnackbar, InjectedNotistackProps } from 'notistack';

import { Foo } from './Foo';
import FooForm from './FooForm';
import { CreateFooMutation, CREATE_FOO } from './CreateFooMutation';
import { FIND_FOOS } from './FindFoosQuery';

interface Props {
  match: {
    params: {
      id: string;
    };
  };
}

type FinalProps = RouteComponentProps & Props & InjectedNotistackProps;

class CreateFoo extends React.Component<FinalProps> {
  render() {
    const {
      match: {
        params: { id },
      },
      enqueueSnackbar,
    } = this.props;

    return (
      <>
        <Typography variant="h1">Create Foo</Typography>

        <CreateFooMutation
          mutation={CREATE_FOO}
          refetchQueries={[{ query: FIND_FOOS }]}
        >
          {(createFoo, { loading, error }) => {
            return (
              <FooForm
                initialValues={{ foo: { title: '' } }}
                onSubmit={async (values, actions) => {
                  try {
                    await createFoo({
                      variables: { foo: values.foo as Foo },
                    });
                  } finally {
                    actions.setSubmitting(false);
                    enqueueSnackbar('Success!');
                  }
                }}
                loading={loading}
                error={error ? error.message : undefined}
              />
            );
          }}
        </CreateFooMutation>
      </>
    );
  }
}

export default withSnackbar(CreateFoo);

The important line is this one:

type FinalProps = RouteComponentProps & Props & InjectedNotistackProps;

Where we're combining the RouteComponentProps interface (pulled from react router), my custom Props interface (declared just above), and the InjectedNotistackProps interface, imported from Notistack. Then we just feed the entire Type to the component.

If you're nesting Higher Order Components (eg: withStyles(styles)(withSnackBar(CreateFoo))), I believe you'll need to take their props into account, too.

@cwbuecheler in that case I'll have to rename InjectedNotistackProps to have a better name such as NotistackProps or withSnackbarProps.

Would be nice if other people who have this issue could confirm the solution.

@iamhosseindhv In my case, the problem was solved by rewriting the mutation from the imperative style to the declarative.
old version:

<Mutation mutation={gql`mutation {createSession} `}>
        {(createSession, {data, loading, error}) => {
            if (error) {
                console.log(JSON.stringify(error));
            }
            if (loading) {
                return <div>Connecting...</div>;
            }
            if (data) {
                sessionStorage.clear();
                props.apolloClient.resetStore().then(() => {
                    let session_token = data.createSession

                    //Notify
                    props.enqueueSnackbar("You successfully logged in", {
                        variant: "success",
                        action: <Button size="small" color="inherit"><Clear color="inherit"/></Button>
                    });
                    Router.push(redirect_url);
                });
                return null;
            } else return (
                drawLogin(createSession, null,)
            )
        }
        }
    </Mutation>

new version:

login() {
        let enqueueSnackbar = this.props.enqueueSnackbar;
        let {t} = this.props;
        return this.props.apolloClient.mutate({
            mutation: gql`
                mutation {
                    createSession
                }
            `
        })
        .then(async (result) => {
            debugger;
            if (result) {
                sessionStorage.clear();
                await this.props.apolloClient.resetStore().then(() => {
                    let session_token = result.data.createSession;

                     //Notify
                    enqueueSnackbar(t("success session"), {
                        variant: "success",
                        action: <Button size="small" color="inherit"><Clear color="inherit"/></Button>
                    });
                    Router.push(redirect_url);
                });
                return null;
            }
        })
        .catch((error) => {
            console.log(JSON.stringify(error));
        });
    }

So, those manipulations do the trick. Works fine with v0.4.2

This worked for me. ExtendInjectedNotistackProps it on top of your existing components' props.

interface ForgotPasswordDialogProps extends RootState, InjectedNotistackProps {}

Be aware InjectedSnackbarProps is going to be renamed to withSnackbarProps in the next release.

This works, maybe this will help someone :)

import { withStyles } from '@material-ui/core';
import { withSnackbar } from 'notistack';
import { withSnackbarProps } from 'notistack';

const styles = ({ palette, spacing }: Theme) => createStyles({
  div: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  margin: {
    margin: 30,
  },
  title: {
    marginTop: 50,
  },
  footer: {
    textAlign: 'center',
    marginTop: 25,
    marginBottom: 100,
  },
});

interface IProps extends WithStyles<typeof styles>, withSnackbarProps { }

class LoginPage extends Component<IProps, {}> {
  public state = {
    submit: '',
    key: '',
  };

  constructor(props: IProps) {
    super(props);
  }

  public render() {
    return (
      <div>
      </div>
    );
  }
}

export default withStyles(styles)(withSnackbar(LoginPage));

import { WithSnackbarProps, withSnackbar } from 'notistack'

This worked for me in ^0.9.5 release. notice case

Was this page helpful?
0 / 5 - 0 ratings

Related issues

khanhdinovative picture khanhdinovative  Â·  3Comments

njetsy picture njetsy  Â·  6Comments

Gareon picture Gareon  Â·  3Comments

james-cordeiro picture james-cordeiro  Â·  6Comments

usama-asfar picture usama-asfar  Â·  3Comments