React类组件中this.context未定义错误的解决方法
问题描述
我需要实现两个功能:
- 保护部分路由,仅允许已认证用户访问(该功能已实现);
- 根据用户是否已认证展示不同的HTML内容(如登录表单或欢迎消息)。
我按照教程使用React Context实现该功能,但运行时在UserGreeting函数中出现以下错误:
undefined is not an object (evaluating 'this.context')
相关代码如下:
src/context/AuthContext.tsx
import { createContext } from "react" interface AuthContext { isAuth: boolean isLoading: boolean } export const AuthContext = createContext<AuthContext>({ isAuth: false, isLoading: true, })
src/hooks/useAuth.ts
import { useEffect, useState } from "react" import isAuthenticated from "../utils/auth" export const useAuth = () => { const [isAuth, setIsAuth] = useState(false) const [isLoading, setIsLoading] = useState(true) useEffect(() => { const checkAuth = async() => { if (await isAuthenticated()) { console.log("Authenticated true") setIsAuth(true) } else { console.log("Authenticated false") setIsAuth(false) } setIsLoading(false) } checkAuth() }, []) return { isAuth, isLoading } }
src/App.tsx
import AppRouter from './routes/Routes' import "./styles/App.sass" import { useAuth } from "./hooks/useAuth" import { AuthContext } from "./context/AuthContext" import Header from "./components/Header/Header" function App() { const { isAuth, isLoading } = useAuth() return ( <AuthContext.Provider value={{ isAuth, isLoading }}> <div className="App"> <Header /> <main className="App-content"> <AppRouter /> </main> </div> </AuthContext.Provider> ) } export default App
src/routes/Routes.tsx
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom' import React, { useState, useEffect, useContext, createContext } from "react" import { useAuth } from "../hooks/useAuth" import Home from "../views/Home" import Panel from "../views/Panel" const ProtectedRoute = ({Component} : { Component: React.ComponentType }) => { const { isAuth, isLoading } = useAuth() if (isLoading) { console.log("Is loading") return null } console.log("isAuthenticated? " + isAuth) if (!isAuth) { return <Navigate to="/" replace /> } return <Component /> } const AppRouter = () => { return ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/panel" element={<ProtectedRoute Component={Panel} />} /> </Routes> </Router> ) } export default AppRouter
src/views/Home.tsx
import React, { Component, useContext } from 'react' import { AuthContext } from "../context/AuthContext" import axios from "axios" import Cookies from "js-cookie" import "../styles/Home.sass" interface HomeProps {} interface HomeState { message: Record<string, string> inputs: Record<string, string> } class Home extends Component<HomeProps, HomeState> { static contextType = AuthContext context!: React.ContextType<typeof AuthContext> constructor(props: HomeProps, context: React.ContextType<typeof AuthContext>) { super(props, context) this.state = { message: {}, inputs: {}, } this.handleChange = this.handleChange.bind(this) this.Form = this.Form.bind(this) } componentDidMount() { } handleChange : React.ChangeEventHandler<HTMLInputElement> = (event) => { let inputs = this.state.inputs inputs[event.target.name] = event.target.value this.setState({inputs}) } render() { return ( <div id="container"> <div id="text"> <div> <h1>Welcome to Finonline</h1> <span id="slogan">Administrate your personal expenses online.</span> <p> This is a demo-project for my job-application showcasing my skills in full-stack webdevelopment, Java, Spring Boot and React.js. </p> </div> </div> <div id="form-container"> <this.UserGreeting /> </div> </div> ) } UserGreeting() { const {isAuth} = this.context if (isAuth) { return ( <div id="welcome"> <span className="title">Welcome </span> <a href="/panel" className="button">Go to panel.</a> </div> ) } else { return <this.Form /> } } Form() { const handleSubmit = (event: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLInputElement, MouseEvent>, action: string) => { event.preventDefault() switch (action) { case "register": this.register(this.state.inputs) break default: this.login(this.state.inputs) } } return ( <form onSubmit={(event) => handleSubmit(event, 'login')} > <span className="title">Login</span> <div className="input-wrapper"> <img src="/assets/images/profile.png" alt="" /> <input type="text" name="name" placeholder="Username" minLength={3} required={true} onChange={this.handleChange} /> </div> <div className="input-wrapper"> <img src="/assets/images/password.png" alt="" /> <input type="password" name="pass" placeholder="Password" minLength={5} required={true} onChange={this.handleChange} /> </div> <div id="form-buttons"> <input type="submit" name="login" value="Login" onClick={(event) => handleSubmit(event, 'login')} /> <input type="submit" name="register" value="Or register" id="registerbtn" onClick={(event) => handleSubmit(event, 'register')} /> </div> <span id="message" className={this.state.message["type"]}> { this.state.message["text"] } </span> </form> ) } async register(data: Record<string, string>) { let message = this.state.message try { const response = await axios.post(`http://127.0.0.1:8080/users`, data , { insecureHTTPParser: true, headers: { "Content-Type": "application/json", } }) if (response.status == 201) { message["type"] = "ok" message["text"] = "Successfully registered." this.setState({ message }) } else { message["type"] = "error" message["text"] = "Error: " + response.data this.setState({ message }) } } catch (err) { message["type"] = "error" message["text"] = "Error: This user already exists." this.setState({ message }) } } async login(data: Record<string, string>) { let message = this.state.message try { const response = await axios.post(`http://127.0.0.1:8080/auth`, data , { insecureHTTPParser: true, headers: { "Content-Type": "application/json", } }) if (response.status == 200) { message["type"] = "ok" message["text"] = "Login successful." this.setState({ message }) const token = response.data.token Cookies.set("auth-token", token, { expires: 1 }) } else { message["type"] = "error" message["text"] = "Error: Login failed." this.setState({ message }) } } catch (err) { message["type"] = "error" message["text"] = "Error: Login failed." console.log(err) this.setState({ message }) } } } export default Home
解决方案
问题根源
UserGreeting是类内部定义的普通函数,并非类的方法,调用时this的指向丢失,导致this.context为undefined。同时Form函数也存在相同的this指向问题,需要一并修复。
修复方式
方式1:绑定this到类方法
在构造函数中为UserGreeting和Form绑定this:
constructor(props: HomeProps, context: React.ContextType<typeof AuthContext>) { super(props, context) this.state = { message: {}, inputs: {}, } this.handleChange = this.handleChange.bind(this) this.Form = this.Form.bind(this) this.UserGreeting = this.UserGreeting.bind(this) // 新增绑定 }
方式2:使用箭头函数定义方法
箭头函数会自动绑定当前类的this,无需手动绑定:
UserGreeting = () => { const {isAuth} = this.context if (isAuth) { return ( <div id="welcome"> <span className="title">Welcome </span> <a href="/panel" className="button">Go to panel.</a> </div> ) } else { return <this.Form /> } } Form = () => { const handleSubmit = (event: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLInputElement, MouseEvent>, action: string) => { event.preventDefault() switch (action) { case "register": this.register(this.state.inputs) break default: this.login(this.state.inputs) } } return ( <form onSubmit={(event) => handleSubmit(event, 'login')} > <span className="title">Login</span> <div className="input-wrapper"> <img src="/assets/images/profile.png" alt="" /> <input type="text" name="name" placeholder="Username" minLength={3} required={true} onChange={this.handleChange} /> </div> <div className="input-wrapper"> <img src="/assets/images/password.png" alt="" /> <input type="password" name="pass" placeholder="Password" minLength={5} required={true} onChange={this.handleChange} /> </div> <div id="form-buttons"> <input type="submit" name="login" value="Login" onClick={(event) => handleSubmit(event, 'login')} /> <input type="submit" name="register" value="Or register" id="registerbtn" onClick={(event) => handleSubmit(event, 'register')} /> </div> <span id="message" className={this.state.message["type"]}> { this.state.message["text"] } </span> </form> ) }
两种方式任选其一即可解决this.context undefined的问题,同时也能避免Form函数中可能出现的this指向错误。
内容的提问来源于stack exchange,提问作者O'Niel
相关产品推荐
相关产品推荐

