用户记录 in/out 状态设置为上下文,并在使用该上下文时产生问题

User logged in/out status set as context, and creating problems when that context is used

在我的组件 write-review.js 中,我需要从当前登录用户中提取 uid
我在 FirebaseContext.js 中有一个函数可以创建一个身份验证侦听器并将身份验证状态设置为状态,然后将该状态转换为上下文以便我可以在我的整个应用程序中使用它:

FirebaseContext.js

import { useState } from 'react'
import { auth } from "./firebaseConfig";
import { onAuthStateChanged } from 'firebase/auth';
import { createContext } from "react";

export const FirebaseContext = createContext()

const FirebaseContextProvider = (props) => {
    
  const [activeUser, setActiveUser] = useState(null);

onAuthStateChanged(auth, (user) => {
    if(user){
        setActiveUser(user)
    }else{
        setActiveUser(null)
    }
})
  return (
    <FirebaseContext.Provider value={{ activeUser }}>
      {props.children}
    </FirebaseContext.Provider>
  );
};

export default FirebaseContextProvider

在我的 write-review.js 文件中,我试图将 uid 从我创建的身份验证侦听器上下文中拉出。然而,这导致了一些奇怪的行为。
有时我能够破坏上下文的uid,如下所示:

const { activeUser } = useContext(FirebaseContext)
const {uid} = activeUser
console.log(uid) //yields the uid

但是有时我会遇到以下两个错误之一:

1.

Uncaught TypeError: Cannot destructure property 'uid' of 'activeUser' as it is null.

Warning: React has detected a change in the order of Hooks called by WriteReview. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks

我很困惑为什么会这样,因为在其他组件中我可以毫无问题地使用这个上下文。
关于如何以这种方式使用上下文从登录用户提取 uid 的任何想法?

项目结构大致如下:

src
|_components
|      |_Header.js
|
|_pages
|   | 
|   |_write-review.js
|
|_firebaseConfig.js
|
|_FirebaseContext.js

这是 write-review.js 组件:

import { useParams,useNavigate } from 'react-router-dom';
import { getAuth } from 'firebase/auth'
import { useState,useContext, useEffect } from 'react'
import { FirebaseContext } from '../FirebaseContext';
import { useVenues } from '../useVenue'
import { GetUsers } from '../useUser';
import  { firebase,FieldValue } from '../firebaseConfig'
import Header from '../components/Header'

const WriteReview =  () => {

    const navigate = useNavigate()
    const { activeUser } = useContext(FirebaseContext)

    // const { userData } = GetUsers()
    
    const [ {
        title,
        review,
        rating,
        ratingService,
        ratingFood,
        ratingValue,
        ratingAtmosphere
    }, setReview ] = useState({
        title:'', 
        review:'', 
        rating:'',
        ratingService:'',
        ratingFood:'',
        ratingValue:'',
        ratingAtmosphere:'',})

     

    let {id} = useParams()
    const { venueData } = useVenues()

    const filteredVenue = venueData.filter(item => {
        return item.id === id
    })

    const handleChange = (e) => {
        const { name,value } = e.target
        e.preventDefault()
        setReview(prevState => ({
            ...prevState,
            [name]:value
        }))
    }

    const handleSubmit = async (e) => {
        const newReview = {
            title:title,
            review:review,
            rating:rating,
            ratingService: ratingService,
            ratingFood: ratingFood,
            ratingValue:ratingValue,
            ratingAtmosphere:ratingAtmosphere
        }
        e.preventDefault()
        await firebase
        .firestore()
        .collection('venues')
        .doc(id)
        .update({
            reviews: FieldValue.arrayUnion(newReview)
        })

        navigate(`/venue/${id}`)
    }


    return(
        <div>
            <Header/>
            <div className='write-review-top'>
                {filteredVenue.map(venue => {
                    return(
                        <div className='write-review-top-img'>
                            <img src = {venue.photoUrl}/>
                            <div className='write-review-top-text'>
                                <p>{venue.name}</p>
                                <p>Venue address</p>
                            </div>
                        </div>
                    )
                })}
            </div>
            <div className='write-review-form'>
                <form onSubmit = {handleSubmit}>
                <h2>Your first hand experiences helps other travellers!</h2>
                    <label>
                        Your overall rating of this venue <br></br>
                        <select name = 'rating' onChange = {handleChange}>
                            <option value = '1'>1</option>
                            <option value = '2'>2</option>
                            <option value = '3'>3</option>
                            <option value = '4'>4</option>
                            <option value = '5'>5</option>
                        </select>
                    </label>
                    <label>Title of your review <br></br>
                        <input type = 'text' name = 'title' value = {title} onChange = {handleChange}/>
                    </label>
                    <label >Your review <br></br>
                        <textarea type = 'text' name = 'review' value = {review} onChange = {handleChange}/>
                    </label>
                    <label>
                        Service <br></br>
                        <select name = 'ratingService' onChange = {handleChange}>
                            <option value = '1'>1</option>
                            <option value = '2'>2</option>
                            <option value = '3'>3</option>
                            <option value = '4'>4</option>
                            <option value = '5'>5</option>
                        </select>
                    </label>
                    <label>
                        Food <br></br>
                        <select name = 'ratingFood' onChange = {handleChange}>
                            <option value = '1'>1</option>
                            <option value = '2'>2</option>
                            <option value = '3'>3</option>
                            <option value = '4'>4</option>
                            <option value = '5'>5</option>
                        </select>
                    </label>
                    <label>
                        Value <br></br>
                        <select name = 'ratingValue' onChange = {handleChange}>
                            <option value = '1'>1</option>
                            <option value = '2'>2</option>
                            <option value = '3'>3</option>
                            <option value = '4'>4</option>
                            <option value = '5'>5</option>
                        </select>
                    </label>
                    <label>
                        Atmosphere <br></br>
                        <select name = 'ratingAtmosphere' onChange = {handleChange}>
                            <option value = '1'>1</option>
                            <option value = '2'>2</option>
                            <option value = '3'>3</option>
                            <option value = '4'>4</option>
                            <option value = '5'>5</option>
                        </select>
                    </label>
                    <button type = 'submit'>Submit</button>
                </form>
            </div>
        </div>
    )
}

export default WriteReview

我怀疑是在 activeUser 状态设置为 null 时您看到了错误。它肯定解释了第一个错误 Uncaught TypeError: Cannot destructure property 'uid' of 'activeUser' as it is null.:

const { activeUser } = useContext(FirebaseContext);
const { uid } = activeUser; // can't destructure from activeUser when null!
console.log(uid);

我猜测,当出现上述错误并抛出时,组件中使用的其他React hook最终没有被调用,从而改变了React框架中hook的整体顺序。

activeUser 为 null 时,您可以提供一个回退值来解构。

const { activeUser } = useContext(FirebaseContext);
const { uid } = activeUser || {}; // <-- provide fallback object to destructure from
console.log(uid); // value or undefined, but OK, won't throw error