Reputation: 2328
I am trying to use a hook but I get the following error when using the useSnackbar hook from notistack.
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
My App.js
<SnackbarProvider
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<App />
</SnackbarProvider>
My SnackBar.js
const SnackBar = (message, severity) => {
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
const action = key => (
<>
<Button
onClick={() => {
closeSnackbar(key)
}}
>
Dismiss
</Button>
</>
)
enqueueSnackbar(message, {
variant: severity,
autoHideDuration: severity === 'error' ? null : 5000,
action,
preventDuplicate: true,
TransitionComponent: Fade,
})
}
My demo.js contains this function
const Demo = props => {
const showSnackBar = (message, severity) => {
SnackBar(message, severity)
}
}
If I were to call the hook in demo.js and pass it in as an argument like the following it works. What is the difference? Why can't I use the useSnackbar() hook in snackbar.js?
const Demo = props => {
const showSnackBar = (message, severity) => {
SnackBar(enqueueSnackbar, closeSnackbar, message, severity)
}
}
Upvotes: 2
Views: 5626
Reputation: 1
UPDATE: The reason you can't call the useSnackbar() in snackbar.js is because snackbar.js is not a functional component. The mighty rules of hooks (https://reactjs.org/docs/hooks-rules.html) state that you can only call hooks from: 1) the body of functional components 2) other custom hooks. I recommend refactoring as you have done to call the hook first in demo.js and passing the response object (along with say the enqueueSnackbar function) to any other function afterwards.
PREVIOUS RESPONSE:
Prabin's solution feels a bit hacky but I can't think of a better one to allow for super easy to use global snackbars.
For anyone getting "TypeError: Cannot destructure property 'enqueueSnackbar' of 'Object(...)(...)' as it is undefined"
This was happening to me because I was using useSnackbar() inside my main app.js (or router) component, which, incidentally, is the same one where the component is initialized. You cannot consume a context provider in the same component that declares it, it has to be a child element. So, I created an empty component called Snackbar which handles saving the enqueueSnackbar and closeSnackbar to the global class (SnackbarUtils.js in the example answer).
Upvotes: 0
Reputation: 11
The Easy way Store the enqueueSnackbar & closeSnackbar in the some class variable at the time of startup of the application, And use anywhere in your application. Follow the steps down below,
1.Store Both enqueueSnackbar & closeSnackbar to class variable inside the Routes.js file.
import React, { Component, useEffect, useState } from 'react';
import {Switch,Route, Redirect, useLocation} from 'react-router-dom';
import AppLayout from '../components/common/AppLayout';
import PrivateRoute from '../components/common/PrivateRoute';
import DashboardRoutes from './DashboardRoutes';
import AuthRoutes from './AuthRoutes';
import Auth from '../services/https/Auth';
import store from '../store';
import { setCurrentUser } from '../store/user/action';
import MySpinner from '../components/common/MySpinner';
import { SnackbarProvider, useSnackbar } from "notistack";
import SnackbarUtils from '../utils/SnackbarUtils';
const Routes = () => {
const location = useLocation()
const [authLoading,setAuthLoading] = useState(true)
//1. UseHooks to get enqueueSnackbar, closeSnackbar
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
useEffect(()=>{
//2. Store both enqueueSnackbar & closeSnackbar to class variables
SnackbarUtils.setSnackBar(enqueueSnackbar,closeSnackbar)
const currentUser = Auth.getCurrentUser()
store.dispatch(setCurrentUser(currentUser))
setAuthLoading(false)
},[])
if(authLoading){
return(
<MySpinner title="Authenticating..."/>
)
}
return (
<AppLayout
noLayout={location.pathname=="/auth/login"||location.pathname=="/auth/register"}
>
<div>
<Switch>
<Redirect from="/" to="/auth" exact/>
<PrivateRoute redirectWithAuthCheck={true} path = "/auth" component={AuthRoutes}/>
<PrivateRoute path = "/dashboard" component={DashboardRoutes}/>
<Redirect to="/auth"/>
</Switch>
</div>
</AppLayout>
);
}
export default Routes;
2. This is how SnackbarUtils.js file looks like
class SnackbarUtils {
#snackBar = {
enqueueSnackbar: ()=>{},
closeSnackbar: () => {},
};
setSnackBar(enqueueSnackbar, closeSnackbar) {
this.#snackBar.enqueueSnackbar = enqueueSnackbar;
this.#snackBar.closeSnackbar = closeSnackbar;
}
success(msg, options = {}) {
return this.toast(msg, { ...options, variant: "success" });
}
warning(msg, options = {}) {
return this.toast(msg, { ...options, variant: "warning" });
}
info(msg, options = {}) {
return this.toast(msg, { ...options, variant: "info" });
}
error(msg, options = {}) {
return this.toast(msg, { ...options, variant: "error" });
}
toast(msg, options = {}) {
const finalOptions = {
variant: "default",
...options,
};
return this.#snackBar.enqueueSnackbar(msg, { ...finalOptions });
}
closeSnackbar(key) {
this.#snackBar.closeSnackbar(key);
}
}
export default new SnackbarUtils();
3.Now just import the SnackbarUtils and use snackbar anywhere in your application as follows.
<button onClick={()=>{
SnackbarUtils.success("Hello")
}}>Show</button>
You can use snackbar in non react component file also
Upvotes: 1
Reputation: 13775
Change
const SnackBar = (message, severity) => { }
to
const SnackBar = ({ message, severity }) => { }
and you have to return some mark-up as well,
return <div>Some stuff</div>
Upvotes: 0
Reputation: 2165
Hooks are for React components which are JSX elements coated in a syntactic sugar.
Currently, you are using useSnackbar()
hook inside SnackBar.js
In order to work, SnackBar.js must be a React component.
Things to check.
"react"
inside the scope of your component.return
a JSX tag for the component to render.For your case,
SnackBar.js
is not a component since it doesn't return anything.demo.js
works because it is a component and it already called the hook and then pass the result down to child function.Upvotes: 0