Ad

User Logged In/out Status Set As Context, And Creating Problems When That Context Is Used

In my component write-review.js, I need to extract the uid from the current logged-in user.
I have a function in FirebaseContext.js that creates an auth listener and sets the auth status to state, and then converts that state into context so I can use it across my entire app:

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

In my write-review.js file, I'm trying to pull the uid out of the auth listener context I created. However this is leading to some strange behaviour.
Sometimes I'm able to destruct the uid of the context, as follows:

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

However sometimes I get one of two errors:

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

I'm confused as to why this is happening, because in other components I'm using this context without problem.
Any ideas on how to extact the uid from a signed in user, using context in this way?

The project structure is roughly as follows:

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

Here's the write-review.js component:

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
Ad

Answer

I suspect that it's when the activeUser state is set to null that you see the error(s). It certainly explains the first error 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);

It is my guess that when the above error occurs and is thrown that the other React hooks used in the component end up not being invoked, and thus the overall order of hooks in the React framework is changed.

You can provide a fallback value to destructure from when activeUser is 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
Ad
source: stackoverflow.com
Ad