当调用从useContext定义的函数时,我收到一个错误,表明它不是函数



我目前是第一次开发web应用程序,其中很多对我来说还是很新的。我通过Context导入到子组件的函数遇到了一些问题。

因此理想的工作流程如下:

  1. 用户访问应用程序并被要求登录。

  2. 用户成功登录,应用程序检索其用户通过一系列API调用配置文件。

  3. 在这些API调用中,我还调用了两个上下文函数I已为组件销毁。

    一个上下文函数设置"0"的状态;loggedIn";值到真实

    另一个上下文函数设置"0"的状态;userState";值到包含userProfile对象。

    这些值也以相同的名称设置到本地存储中。

  4. 从这里开始,状态已经更新,因此页面应该重新呈现并利用来自全局状态的新可用信息。

  5. 随后,用户应该能够刷新页面,而不是需要重新登录,因此在组件开始时,我还调用上下文函数并传入存储在本地存储以使它们保持最新。

实际发生的情况:

为了提供进一步的上下文,我的web应用程序具有多个";选项卡";在标头中,当选择时将在父级中呈现不同的组件。这就是问题发生的地方。即使在刷新时,上述工作流似乎也能正常工作,但当我单击其组件试图调用上下文函数的选项卡时,我会收到一个错误,说明TypeError:functionName不是函数。以下是出现的错误图像:非功能错误

我一直无法弄清楚为什么这是一个问题,我真的需要一只手来弄清楚,因为我更希望能够在数据更改时自动重新渲染我的组件,而不需要调用一个函数来手动强制重新渲染我所有的组件。下面是我的一些代码片段,试图帮助更好地说明这个问题。

//**************************
//GlobalState.js
//**************************
import React, {createContext, useReducer} from 'react';
import AppReducer from './AppReducer';
const initialState = {
loggedIn                : false,
userState               : "undefined",
selectedTab             : "yourProfile"
}
export const GlobalContext = createContext(initialState);
export const GlobalProvider = ({children}) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
function logInContext(response){
console.log("logInContextCalled")
dispatch({
type    : 'LOG_IN',
payload : (response)
});
}
function getUPContext(response){
console.log("getUPContext called")
dispatch({
type    : 'GET_UP',
payload : (response)
})
}
function tabContext(response){
dispatch({
type    : 'SET_TAB',
payload : (response)
})
}
return(
<GlobalContext.Provider value = {{loggedIn : state.loggedIn, logInContext, userState : state.userState, getUPContext, selectedTab : state.selectedTab, tabContext}}>
{children}
</GlobalContext.Provider>
)
}
//**************************
//AppReducer.js
//**************************
import React from 'react';
export default (state, action) => {
switch(action.type){
case 'LOG_IN':{
localStorage.setItem("loggedIn", action.payload)
return {...state, loggedIn: action.payload};
}
case 'GET_UP' :{
localStorage.setItem("userState", JSON.stringify(action.payload))
return {...state, userState: action.payload};
}
case 'SET_TAB' :{
localStorage.setItem("selectedTab", action.payload)
return{...state, selectedTab: action.payload}
}
}
}
//**************************
//your-profile.js
//**************************
import styles from "../styles/yourProfile.module.css"
import Link from 'next/link'
import Image from 'next/image'
import qrCode from '../public/frame.png'
import { GlobalContext } from "../pages/context/GlobalState.js"
import AppReducer from '../pages/context/AppReducer'
import {useContext, useEffect, useState} from 'react'
//let loggedIn;
//let userState;
export default function YourProfile(){
const {logInContext, getUPContext, loggedIn, userState, selectedTab, tabContext} = useContext(GlobalContext);
//let update = forceUpdate();
//THESE FUNCTIONS THROW THE NOT A FUNCTION ERROR WHEN CALLED. ONLY OCCURS WHEN SELECTING THE TAB TO RENDER. NOT CURRENTLY HAPPENING ON REFRESH.
useEffect(() => {
console.log("use effect called")
logInContext(localStorage.getItem("loggedIn"))
getUPContext(JSON.parse(localStorage.getItem("userState")))
//update()
}, [])

if(loggedIn)console.log("USER IS LOGGED IN")
console.log("Value of logged in and userState inside your-Profile")
console.log(loggedIn)
console.log(userState)
if(loggedIn && userState){
return(
//render something
)
}
else{
return(
//Render something elsse
)
}
}
//**************************
//_app.js
//**************************
import '../styles/globals.css'
import {GlobalProvider} from './context/GlobalState'
function MyApp({ Component, pageProps }) {
return(
<GlobalProvider>
<Component {...pageProps} />
</GlobalProvider>
);
}
export default MyApp

我在useCallback中封装了传递到上下文中的函数——由于依赖数组为空,这将确保它们只定义一次。这是安全的,因为唯一的依赖是调度,它保证是稳定的。我还破坏了状态,这样您就可以通过状态变量的名称来引用它们。

我还删除了函数中响应周围的括号。我认为这可能会有所帮助,但我没有完整的代码来测试它

import React, { createContext, useReducer, useCallback } from 'react'
import AppReducer from './AppReducer'
const initialState = {
loggedIn: false,
userState: 'undefined',
selectedTab: 'yourProfile',
}
export const GlobalContext = createContext(initialState)
export const GlobalProvider = ({ children }) => {
const [{ loggedIn, userState, selectedTab }, dispatch] = useReducer(
AppReducer,
initialState
)
const logInContext = useCallback((response) => {
console.log('logInContextCalled')
dispatch({
type: 'LOG_IN',
payload: response,
})
}, [])
const getUPContext = useCallback((response) => {
console.log('getUpContext called')
dispatch({
type: 'GET_UP',
payload: response,
})
}, [])
const tabContext = useCallback((response) => {
console.log('setTabContext called')
dispatch({
type: 'SET_TAB',
payload: response,
})
}, [])
return (
<GlobalContext.Provider
value={{
loggedIn,
logInContext,
userState,
getUPContext,
selectedTab,
tabContext,
}}
>
{children}
</GlobalContext.Provider>
)
}

我知道这是你的第一个网络应用程序,所以当谈到React时,我会尝试解释一些基本的事情。有许多";坏的";教程,教坏习惯。

其中一个坏习惯是利用useEffect编写命令式React代码——当效果触发时进行微观管理。尽量避免这种情况。我知道有时候会很难。当您访问useEffect(包括函数)内的道具、状态或上下文中的数据时,请将其添加到依赖数组中。这将确保在数据发生变化时触发效果。这就是为什么应该避免向组件(和上下文)提供不稳定的函数。

如果您为useEffect提供了一个空的依赖数组,它将只运行一次——当组件装载时。也就是说,除非你在React 18上并且处于严格模式。然后它将触发两次。小心使用useEffect。

我还建议你以不同的方式命名你的函数。不同的名称不会改变行为,但当函数具有合理的名称时,使用代码进行推理会更容易。一些可供选择的函数名称可以是logIngetProfilesetTab。在他们的名字中添加上下文只是令人困惑。

话虽如此,与useState相比,使用useReducer的一个优点是调度功能是稳定的,并且可以传递给其他组件。这允许您定义传递到其他地方调度的操作。例如,您可以简单地将dispatch添加到上下文中,并在更接近触发它们的地方定义函数。也许这会导致代码更容易调试。

我还看到您可以在reducer和dispatch函数中访问localStorage。一般来说,Reducer函数应该是纯函数,它们不应该引起副作用,例如写入localStorage。我偷偷怀疑,向localStorage写入和从localStorage读取内容会给您带来问题。当您将localStorage.getItem('loggedIn')提供给logInContext时,会出现错误。试着把这段逻辑分成不同的部分,看看会发生什么。

const storedValue = localStorage.getItem('loggedIn')
console.log({storedValue, logInContext})
logInContext(storedValue)

这将记录值和函数,这样您就可以看到发生错误时它们是什么。

很抱歉,我不能准确地指出问题所在,但我希望我的一些建议能帮助你自己找到问题所在。