0

I am trying to use context with my Gatsby project. I have successfully implemented this in my previous project and I have copied the code over to my new project and it's not working as intended.

This is my context.js file:

import React, { useContext, useState } from "react";

const defaultState = {
  isLoggedIn: false,
};

const AuthContext = React.createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  function toggle() {
    console.log("BOO!");
  }

  const value = {
    isLoggedIn,
    setIsLoggedIn,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

This is my app.js file:

import React from "react";
import { Router } from "@reach/router";
import IndexPage from "./index";
import ProjectPage from "./project";
import { AuthProvider } from "../contexts/context";

const App = () => (
  <AuthProvider>
    <Router basepath="/app">
      <IndexPage path="/" component={IndexPage} />
      <ProjectPage path="/project" component={ProjectPage} />
    </Router>
  </AuthProvider>
);

export default App;

This is my index.js file:

import React, { useContext } from "react";
import { Link } from "gatsby";
import { useAuth } from "../contexts/context";
import { AuthContext } from "../contexts/context";

const IndexPage = () => {

  console.log(useAuth())
  return (
    <div className="w-40 h-40 bg-red-400">
      {/*<Link to="/project">to projects</Link>*/}
      <div>Click me to toggle: uh</div>
    </div>
  );
};

export default IndexPage;

useAuth() should return the desired components and functions but instead is always returning undefined. I have looked over my previous code as well as snippets on stack overflow and I can't seem to find the correct fix.


The following includes code that successfully built and executed:

Original context.js

import '@stripe/stripe-js'

/* Functionality */
import React, { useContext, useEffect, useState } from "react";
import { navigate } from "@reach/router";
import firebase from 'gatsby-plugin-firebase';
import { useLocalStorage } from 'react-use';

const AuthContext = React.createContext()

export function useAuth() {
    return useContext(AuthContext)
}

export function AuthProvider({ children }) {
    
    const [isLoggedIn, setIsLoggedIn] = useState(false)

    
    const [isLoading, setIsLoading] = useLocalStorage("loading", false);

    // Sign In
    const signInWithRedirect = (source) => {

        let provider;
        switch(source) {
            case 'Google':
                provider = new firebase.auth.GoogleAuthProvider()
                break;
            case 'Github':
                provider = new firebase.auth.GithubAuthProvider()
                break;
            default:
                break;
        }

        setIsLoading(true)

        firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
            .then(() => {
                // Existing and future Auth states are now persisted in the current
                // session only. Closing the window would clear any existing state even
                // If a user forgets to sign out.
                // ...
                // New sign-in will be persisted with session persistence.
                
                return firebase.auth().signInWithRedirect(provider)
            })
            .catch((error) => {
                // Handle Errors here.
                let errorCode = error.code;
                let errorMessage = error.message;
            });      
    }   

    // Sign Out
    const signOut = () => {
        firebase.auth().signOut().then(() => {
            // Sign-out successful.
            setIsLoggedIn(false)
            navigate('/app/login')
          }).catch((error) => {
            // An error happened.
          });
    }

    useEffect(() => {    

        firebase.auth().onAuthStateChanged((user) => { 
            
            try {

                // If user is authenticated
                if (!!user) {

                    // Fetch firestore document reference
                    var docRef = firebase.firestore().collection("study_guide_customers").doc(user.uid)

                    docRef.get().then((doc) => {

                        console.log('checking doc')
                        
                        // If the document doesn't exist, create it and add to the firestore database
                        if (!doc.exists) {

                            console.log('inside customer')

                            const customer = {
                                customerCreationTimestamp: firebase.firestore.Timestamp.now(),
                                username: user.displayName,
                                email: user.email
                            }

                            firebase.firestore().collection("study_guide_customers").doc(user.uid).set(customer)
                            .then(() => {
                                // After docuement for user is created, set login status
                                setIsLoggedIn(!!user)
                                setIsLoading(false)
                            })
                            .catch((error) => {
                                console.error("Error writing document: ", error);
                            });

                        // If document for user exists, set login status
                        } else {
                            setIsLoggedIn(!!user)
                            setIsLoading(false)
                        }     
                    })
                }                
            } catch {
                console.log('Error checking firestore existence and logging in...')
            }  

        })

    }, [isLoggedIn, isLoading, setIsLoading, setIsLoggedIn])

    const value = {
        signOut,
        isLoggedIn,
        isLoading,
        setIsLoading,
        setIsLoggedIn,
        signInWithRedirect,
    }

    return (
        <AuthContext.Provider value={value}>
            {children}
        </AuthContext.Provider>
    )
}

Original app.js

/* Stripe Security */
import '@stripe/stripe-js'

/* Functionality */
import React from "react"
import { Router } from "@reach/router"
import PrivateRoute from "../components/PrivateRoute"
import Profile from "../components/Profile"
import Login from "../components/Login"
import Projects from "../components/Projects"
import IndexPage from "./index"
import NotFoundPage from './404'
import { AuthProvider } from "../contexts/context"


const App = () => (
  <AuthProvider>
    <Router basepath="/app">
      <PrivateRoute path="/profile" component={Profile} />
      <Login path="/login" component={Login}/>
      <IndexPage path="/"  component={IndexPage}/>
      <Projects path="/projects" component={Projects} />
    </Router>
  </AuthProvider>
)

export default App

Original index.js

/* Stripe Security */
import '@stripe/stripe-js'

/* Functionality */
import * as React from "react"
import  IndexContact  from "../components/Index/Contact"
import IndexSelectedProjects from "../components/Index/SelectedProjects"
import IndexFeaturedProjects from "../components/Index/FeaturedProjects"
import IndexFooter from "../components/Index/Footer"
import IndexStudyGuide from "../components/Index/StudyGuide"
import IndexNavbar from "../components/Index/Navbar"
import IndexHeader from "../components/Index/Header"
import IndexAbout from '../components/Index/About'
import IndexExperience from '../components/Index/Experience'

import { useMount } from 'react-use';

const IndexPage = () => {

  useMount(() => localStorage.setItem('loading', false));

  return (
    <>
      <IndexNavbar />
      <IndexHeader />
      <IndexAbout />
      <IndexExperience />
      <IndexFeaturedProjects />
      <IndexSelectedProjects />
      <IndexStudyGuide />
      <IndexContact />
      <IndexFooter />   
    </>
  )
}

export default IndexPage

Then in any component I could simply use the following code to access the context

import { useAuth } from "../contexts/context"

const { isLoggedIn, signInWithRedirect, isLoading } = useAuth()
Yerin
  • 1
  • 1

1 Answers1

0

Child components are mounted before parent. Fix your context.js file to add a default value for isLoggedIn state:

const defaultState = {
  isLoggedIn: false,
  setIsLoggedIn: () => {}
};

const AuthContext = React.createContext(defaultState);

Your defaultState should also include default methods for any parts of the context you wish to work with.

smac89
  • 39,374
  • 15
  • 132
  • 179
  • Thank you. Though I understand how this works for booleans I am not sure how to handle functions. For example, if I want to add a function to toggle the isLoggedIn state in AuthProvider and call useAuth(), it gives an error saying it's not a function. Do all my functions need to be defined in defaultState too? – Yerin Sep 17 '22 at 21:57
  • @Yerin The way you do it is to create a function that does nothing...I will update my answer – smac89 Sep 17 '22 at 22:12
  • @Yerin, I'm starting to have doubts that this is the correct solution. I believe the way you've declared the `AuthProvider` should be enough to get the correct context to any child that needs it... – smac89 Sep 17 '22 at 22:18
  • @Yerin have a look at this other question which is very similar to yours https://stackoverflow.com/questions/70259056/why-is-context-undefined-in-child-component – smac89 Sep 17 '22 at 22:21
  • That removed the error, but the function doesn't get called. I'll update the code to show the original code I used in my previous project where it worked correctly. With respect to the default, "The defaultValue argument is only used when a component does not have a matching Provider above it in the tree." which shouldn't be the case in my scenario since there is a provider above it in the tree. – Yerin Sep 17 '22 at 22:31