【问题标题】:Unexpected output using react-router-dom with React's Context API使用带有 React 的 Context API 的 react-router-dom 的意外输出
【发布时间】:2021-06-08 12:09:40
【问题描述】:

对于我的一个小项目,我正在尝试尽可能实现最基本的身份验证,使用没有 Redux 的 React 上下文 API。

import { createContext, useContext, useState } from 'react'

export const AuthContext = createContext()

export const useAuth = () => {
    const context = useContext(AuthContext)

    if(context === null) throw new Error('Nope')

    return context  
}

export const AuthProvider = (props) => {
    const [authenticated, setAuthenticated] = useState(false)

    const login = () => {
        setAuthenticated(true)

        localStorage.setItem(storageKey, true)
    }
    
    const logout = () => {
        setAuthenticated(false)

        localStorage.setItem(storageKey, false)
    }
    
    return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}

export default AuthContext

我创建了一个上下文,并像这样将我的&lt;App /&gt; 组件包裹在其中; &lt;AuthProvider&gt;&lt;/App&gt;&lt;/AuthProvider&gt;。因为我想保持认证状态,所以我使用浏览器的本地存储来存储一个简单的布尔值。

import PrivateRoute from './PrivateRoute'
import { useAuth } from './context/AuthContext'

import { AuthPage } from './pages'

import {
    BrowserRouter,
    Switch,
    Route,
} from 'react-router-dom'

import { useEffect } from 'react'

const App = () => {
    const { login, authenticated } = useAuth()

    useEffect(() => {
        const token = localStorage.getItem('user')

        if(token && token !== false) { login() }
    })

    return (
        <BrowserRouter>
            <Switch>
                <PrivateRoute exact path="/auth" component={AuthPage} />
                <Route exact path='/'>
                    Dashboard
                </Route>
            </Switch>
        </BrowserRouter>
    )
}

export default App

然后,在我的&lt;App /&gt; 组件中,我尝试调用从AuthProvider 给出的login 回调,这让我认为这让我在页面刷新期间登录。当我尝试访问当前组件中的authenticated 变量时,它确实有效。它表明我已通过身份验证。

但是,当我尝试设置 PrivateRoute 时,只有经过身份验证的用户才能这样访问:

import {
    Route,
    Redirect
} from 'react-router-dom'

import { useAuth } from './context/AuthContext'

const PrivateRoute = ({ component: Component, ...rest }) => {
    const { authenticated } = useAuth()
    
    if(authenticated) {
        return <Route {...rest} render={(props) => <Component {...props} />} />
    }

    return <Redirect to={{ pathname: '/login' }} />
}

export default PrivateRoute

它不起作用。它只是将我重定向到登录页面。这是怎么来的? PrivateRoute 组件正在从 &lt;App /&gt; 组件呈现。还有,这个问题有什么解决办法?

【问题讨论】:

  • useEffect 被调用 after PrivateRoute 组件已经挂载,当它调用它又调用login 函数时,用户已经被重定向到@ 987654336@路线。
  • PrivateRoute 组件内,仅在调用login 函数后检查authenticated 的值。创建了一个demo,向您展示如何解决问题。 (尝试更改 useEffect 挂钩内的 token 变量的值)
  • 上面的这个演示很棒。似乎最好只跟上身份验证的加载状态,这允许组件呈现,但您可以使用加载状态基本上等待身份验证发生。

标签: javascript reactjs react-router-dom react-context


【解决方案1】:

与其在每次重新渲染时运行useEffect 来检查用户是否应该登录,不如使用localStorage 中的值初始化authenticated 状态:

const storageKey = 'user'
const initialState = JSON.parse(localStorage.getItem(storageKey)) ?? false

export const AuthProvider = (props) => {
  const [authenticated, setAuthenticated] = useState(initialState)

  const login = () => {
      setAuthenticated(true)

      localStorage.setItem(storageKey, true)
  }
  
  const logout = () => {
      setAuthenticated(false)

      localStorage.setItem(storageKey, false)
  }
  
  return <AuthContext.Provider value={{authenticated, login, logout}} {...props}/>
}

【讨论】:

    【解决方案2】:

    感谢Yousaf在cmets和GitHub上HospitalRun项目中的解释,我在&lt;App /&gt;组件中做了一个加载状态。

    import { useAuth } from './context/AuthContext'
    import { useEffect, useState } from 'react'
    
    import Router from './Router'
    
    const App = () => {
        const [ loading, setLoading ] = useState(true)
        const { login } = useAuth()
    
        const token = localStorage.getItem('user')
    
        useEffect(() => {
            if(token && token !== false) { 
                login()
            }
    
            setLoading(false)
        }, [loading, token, login])
    
        if (loading) return null
    
        return <Router />
    }
    
    export default App
    

    这里我只让任何东西渲染,在login函数被调用之后。

    if (loading) return null
    

    如果这可以做得更好,仍然会收到反馈!

    【讨论】:

    • 我会说loading 是一个冗余状态,因为它与authenticated 是相反的布尔值,对吧?我想您可以改为使用authenticated。似乎您的 useEffect 用于初始状态目的,因为每次后续执行都来自 login|out 在其他地方被调用。如果您按照我的建议尝试使用 localStorage 值(这是一种常用方法)初始化您的状态,我相信如果我没记错的话,您只能直接检查authenticated
    猜你喜欢
    • 2021-08-03
    • 2021-07-30
    • 2021-02-19
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多