【问题标题】:Unable to use a hook in a component无法在组件中使用挂钩
【发布时间】:2020-08-13 01:51:18
【问题描述】:

我正在尝试使用钩子,但在使用来自 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)
    }
}

【问题讨论】:

  • 您的snackbar.js 似乎不是一个组件。你只能在组件内部使用钩子。
  • 我明白了,在snackbar.js 中调用SnackBar 函数的最佳方式是什么?我应该从我调用它的任何组件传入 enqueueSnackbar 和 closeSnackbar 函数吗?

标签: reactjs react-hooks notistack


【解决方案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 并在您的应用程序的任何位置使用 snapbar,如下所示。

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

你也可以在非反应组件文件中使用snackbar

【讨论】:

  • 感谢您的信息。我不知道这些事情。很抱歉。
  • 没问题!既然你已经看到了,我已经删除了我原来的评论。
  • TypeError:无法解构“对象(...)(...)”的属性“enqueueSnackbar”,因为它未定义。这是因为行const { enqueueSnackbar, closeSnackbar } = useSnackbar();
【解决方案2】:

Hooks 用于 React 组件,这些组件是包裹在语法糖中的 JSX 元素。

目前,您在 SnackBar.js

中使用 useSnackbar() 挂钩

为了工作,SnackBar.js 必须是 React 组件。

检查事项。

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

对于你的情况,

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

【讨论】:

    【解决方案3】:

    改变

    const SnackBar = (message, severity) =&gt; { }

    const SnackBar = ({ message, severity }) =&gt; { }

    您还必须返回一些标记,

    return &lt;div&gt;Some stuff&lt;/div&gt;

    【讨论】:

      【解决方案4】:

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

      之前的回复:

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

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

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

      【讨论】:

        猜你喜欢
        • 2021-09-12
        • 2023-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-09
        相关资源
        最近更新 更多