简体   繁体   English

无法在组件中使用挂钩

[英]Unable to use a hook in a component

I am trying to use a hook but I get the following error when using the useSnackbar hook from 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

My App.js我的 App.js

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

My SnackBar.js我的 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我的 demo.js 包含这个 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.如果我要调用 demo.js 中的钩子并将其作为参数传入,如下所示。 What is the difference?有什么区别? Why can't I use the useSnackbar() hook in snackbar.js?为什么我不能在snackbar.js 中使用useSnackbar() 挂钩?

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

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.简单的方法在应用程序启动时将 enqueueSnackbar 和 closeSnackbar 存储在一些 class 变量中,并在应用程序的任何地方使用。 Follow the steps down below,按照下面的步骤,

1.Store Both enqueueSnackbar & closeSnackbar to class variable inside the Routes.js file. 1.将 enqueueSnackbar 和 closeSnackbar 都存储到 Routes.js 文件中的 class 变量中。

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 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.Now just import the SnackbarUtils and use snackbar anywhere in your application as follows. 3.现在只需导入 SnackbarUtils 并在您的应用程序中的任何位置使用 snapbar,如下所示。

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

You can use snackbar in non react component file also您也可以在非反应组件文件中使用snackbar

Hooks are for React components which are JSX elements coated in a syntactic sugar. Hooks 用于 React 组件,这些组件是包裹在语法糖中的 JSX 元素。

Currently, you are using useSnackbar() hook inside SnackBar.js目前,您在SnackBar.js中使用useSnackbar()挂钩

In order to work, SnackBar.js must be a React component.为了工作, SnackBar.js必须是一个 React 组件。

Things to check.要检查的东西。

  1. If you have imported React from "react" inside the scope of your component.如果你已经从组件的 scope 中的"react"导入了 React。
  2. If you have return a JSX tag for the component to render.如果您已return要呈现的组件的 JSX 标记。

For your case,对于你的情况,

  • Your SnackBar.js is not a component since it doesn't return anything.您的SnackBar.js不是一个组件,因为它不返回任何内容。
  • Your demo.js works because it is a component and it already called the hook and then pass the result down to child function.您的demo.js可以工作,因为它是一个组件并且它已经调用了钩子,然后将结果传递给子 function。

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>

UPDATE: The reason you can't call the useSnackbar() in snackbar.js is because snackbar.js is not a functional component.更新:你不能在snackbar.js 中调用useSnackbar() 的原因是snackbar.js 不是一个功能组件。 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.钩子的强大规则( https://reactjs.org/docs/hooks-rules.html )state,你只能从其他功能组件调用钩子1.body) 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.我建议您进行重构,首先在 demo.js 中调用钩子,然后将响应 object(以及 enqueueSnackbar 函数)传递给任何其他 function。

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. Prabin 的解决方案感觉有点老套,但我想不出更好的解决方案来允许超级易于使用的全球小吃店。

For anyone getting "TypeError: Cannot destructure property 'enqueueSnackbar' of 'Object(...)(...)' as it is undefined"对于任何收到“TypeError:无法解构'Object(...)(...)'的属性'enqueueSnackbar'的人,因为它是未定义的”

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.这发生在我身上,因为我在我的主 app.js(或路由器)组件中使用了 useSnackbar(),顺便提一下,该组件与初始化组件的位置相同。 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).因此,我创建了一个名为 Snackbar 的空组件,它处理将 enqueueSnackbar 和 closeSnackbar 保存到全局 class(示例答案中的 SnackbarUtils.js)。

For anyone who wanted to create hook for reusability: custom react hook to add variations like Close Button etc.对于任何想要创建可重用钩子的人:自定义反应钩子以添加诸如关闭按钮等变体。

import { useSnackbar } from 'notistack';

export const useNotistack = () => {
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
  const generateSnackbar = (message) =>
     enqueueSnackbar(message, {
     variant: 'default',
     action: (key) => {
       return (
         <IconButton onClick={() => closeSnackbar(key)} id={key}/>
      );
   },
 });

 return generateSnackbar;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM