无法在组件中使用钩子

时间:2020-08-13 01:51:18

标签: reactjs react-hooks notistack

我正在尝试使用钩子,但是从notistack使用useSnackbar钩子时出现以下错误。

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

我的App.js

 <SnackbarProvider
      anchorOrigin={{
        vertical: 'top',
        horizontal: 'center',
      }}
    >
      <App />
 </SnackbarProvider>

我的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,
  })
}

我的demo.js包含此功能

const Demo = props => {
    const showSnackBar = (message, severity) => {
      SnackBar(message, severity)
    }
}

如果我要在demo.js中调用该钩子,并将其作为参数传递,如下所示,它将起作用。有什么不同?为什么我不能在Snackbar.js中使用useSnackbar()钩子?

const Demo = props => {
    const showSnackBar = (message, severity) => {
      SnackBar(enqueueSnackbar, closeSnackbar, message, severity)
    }
}

4 个答案:

答案 0 :(得分:1)

简便方法 在启动应用程序时,将enqueueSnackbar和closeSnackbar存储在某个类变量中,并在应用程序中的任何位置使用。 按照下面的步骤进行操作

1。将enqueueSnackbar和closeSnackbar都存储到Routes.js文件中的类变量。

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。这就是SnackbarUtils.js文件的样子

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。现在,只需导入SnackbarUtils并在应用程序中的任何位置使用小吃栏,如下所示。

<button onClick={()=>{
           SnackbarUtils.success("Hello")
        }}>Show</button>

您还可以在非反应组件文件中使用小吃栏

答案 1 :(得分:0)

钩子是用于React组件的,钩子是涂有语法糖的JSX元素。

当前,您正在使用 SnackBar.js

中的useSnackbar()钩子

为了正常工作, SnackBar.js 必须是一个React组件。

要检查的东西。

  1. 如果您是在组件范围内从"react"导入React的。
  2. 如果您有return个JSX标签以供组件渲染。

对于您的情况,

  • 您的SnackBar.js不是组件,因为它不返回任何内容。
  • 您的demo.js之所以有效,是因为它是一个组件,它已经调用了钩子,然后将结果传递给子函数。

答案 2 :(得分:0)

更改

const SnackBar = (message, severity) => { }

const SnackBar = ({ message, severity }) => { }

,您还必须返回一些标记,

return <div>Some stuff</div>

答案 3 :(得分:0)

更新:你不能在snackbar.js 中调用useSnackbar() 的原因是因为snackbar.js 不是一个功能组件。钩子的强大规则 (https://reactjs.org/docs/hooks-rules.html) 规定您只能从以下位置调用钩子:1) 功能组件的主体 2) 其他自定义钩子。我建议重构,就像您在 demo.js 中首先调用钩子一样,然后将响应对象(以及 enqueueSnackbar 函数)传递给任何其他函数。

之前的回复:

Prabin 的解决方案感觉有点老套,但我想不出更好的解决方案来实现超级易用的全球小吃店。

对于任何获得 “类型错误:无法解构 'Object(...)(...)' 的属性 'enqueueSnackbar',因为它未定义”

这发生在我身上,因为我在我的主要 app.js(或路由器)组件中使用了 useSnackbar(),顺便提一下,它与组件初始化的地方相同。您不能在声明它的同一组件中使用上下文提供程序,它必须是子元素。因此,我创建了一个名为 Snackbar 的空组件,它负责将 enqueueSnackbar 和 closeSnackbar 保存到全局类(示例答案中的 SnackbarUtils.js)。