【问题标题】:Getting Invalid hook call error on importing a component in React在 React 中导入组件时出现无效的钩子调用错误
【发布时间】:2021-07-25 21:20:21
【问题描述】:

我已经建立了一个登录页面,在填写用户名和密码并点击提交按钮时,它会调度基本上转到后端的操作并检查用户名和密码是否有效。我正在使用Reactredux 这是我下面的登录页面

login.js(简体)

import LoginHandle from '../login/LoginHandle'

function userLogin(event) {
  event.preventDefault()
  const username = document.getElementById("username").value
  const password = document.getElementById("password").value
  
  LoginHandle(username, password)

}

const Login = () => {
  return (
    <div className="flex-row align-items-center">
                  <form onSubmit={(e) => userLogin(e)}>
                    <h1>Login</h1>
                    
                      <input id="username" type="email" placeholder="Username" required/>
                 
                      <input id="password" type="password" placeholder="Password" required/>
      
                        <button type="submit" className="px-4">Login</button>
                </form>
    </div>
  )
}

export default Login

LoginHandle.js(简化版)

import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'


const LoginHandle = (username, password) => {

    const dispatch = useDispatch()
    dispatch(getLoginStatus(username, password))
    const loginState = useSelector(state=> state.loginStatus) 

    if(loginState.loading) {
        console.log("please wait")
    }

    // .... rest of the logic
}

export default LoginHandle

如您所见,我尝试发送操作,然后检查状态以从后端获得确认。但是我得到了这个错误

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:...

我做错了什么?

【问题讨论】:

  • 钩子用于功能组件。您在一个简单的函数中调用它们,这是不允许的,错误意味着什么。
  • @nil 您好,nil,是的,我想将登录详细信息的处理分离到一个单独的文件中,而不是在 Login 组件本身中进行。有可能吗?

标签: reactjs react-redux react-hooks redux-thunk


【解决方案1】:
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'

const useLoginHandler = (username, password, submitted) => {
     const dispatch = useDispatch();
     const loginState = useSelector(state=> state.loginStatus)
     // it is better to put the dispatch inside a useEffect rather than outside
     useEffect(() => {
        if(!submitted) {return};
        dispatch(getLoginStatus(username, password))
     }, [username, password, submitted]);


    if(loginState.loading) {
        console.log("please wait")
    }

    // .... rest of the logic
}

export default useLoginHandler

而且你对钩子的使用是错误的。它不应该在回调函数中。 它应该在顶层。像下面的东西。另外你不应该直接访问 dom 元素,而是使用 useRef 或 useState 来获取值

const Login = () => {
   const [username, setUsername] = useState('');
   const [password, setPassword] = useState('')
   const [submitted, setSubmitted] = useState(false);
   useLoginHandler(username, password, submitted); //custom hook should be used at this level
   return (
      <form onSubmit={(e) => setSubmitted(true)}>
      <input id="username" type="email" placeholder="Username" required onChange={e => setUsername(e.target.value)}/>
          </form>   
   )
}

【讨论】:

  • 嗨拉维,感谢您的回复。这就是我一直在寻找的东西,而自定义钩子就是答案。我会接受这一点。您还提到不应直接访问 Dom 元素。我可以知道这种方法有什么问题吗?
  • 有时在您执行该查询时实际的 dom 元素可能不可用。在这种情况下它可能会起作用,但通常最好使用 useRef 来访问 DOM
【解决方案2】:

您在此实现中使用了 2 个钩子 useDispatchuseSelector 并在函数中调用它们,这是无效的用法。钩子只能在功能组件中调用。因此,如下更改您的上述实现应该可以:

Login.js

import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'

const Login = () => {
  
  const dispatch = useDispatch()
  const loginState = useSelector(state=> state.loginStatus) 

  function userLogin(event) {
    event.preventDefault()
    const username = document.getElementById("username").value
    const password = document.getElementById("password").value
  
    dispatch(getLoginStatus(username, password))
  }


  return (
    <div className="flex-row align-items-center">
                  <form onSubmit={(e) => userLogin(e)}>
                    <h1>Login</h1>
                    
                      <input id="username" type="email" placeholder="Username" required/>
                 
                      <input id="password" type="password" placeholder="Password" required/>
      
                        <button type="submit" className="px-4">{loginState.loading ? "Please wait..." : "Login"}</button>
                </form>
    </div>
  )
}

export default Login

注意事项:

  • 直接在功能组件中调用 useDispatch,而不是在功能组件的函数内部。然后我们可以在函数内部使用 useDispatch 的结果
  • 在功能组件内调用 useSelector,然后使用输出显示加载代替按钮中的文本

【讨论】:

  • 嗨哈希尔,感谢您的回复。是的,我知道我可以在 Login 组件内部调用它并且它会起作用。但我想将登录详细信息的处理分离在一个单独的文件中。有可能吗?
  • 是的,这是可能的。我们通常在 redux action 中处理所有的处理,然后 reduce 然后直接在功能组件中 dispatch 这些 action。我们不能在功能组件之外调用 useDispatch。因此,只需通过 redux reducers/actions 实现您的登录逻辑,并从该组件调度登录操作。
  • 我确实有一个 reducer 和 action,实际上 getLoginStatus 函数实际上是从一个 redux action 导入的,它对后端进行 api 调用。我只是想将表单验证和发布 api 操作分开在一个单独的文件中。
  • 我建议您阅读一些使用 hooks 和 redux 的文章,以便您更好地了解其实现方式。这基本上是使用 hooks 和 redux 的标准方式(我猜是唯一可能的方式)。
猜你喜欢
  • 2020-08-01
  • 2021-04-15
  • 2020-12-16
  • 1970-01-01
  • 1970-01-01
  • 2022-06-15
  • 2021-11-03
  • 2023-02-09
  • 1970-01-01
相关资源
最近更新 更多