Home > Software engineering >  React-Router-Dom unable to render page but routes back due to PrivateRoute
React-Router-Dom unable to render page but routes back due to PrivateRoute

Time:04-01

I am having some issues with my routing currently when authenticated. Whenever I try to access my ViewPortfolio page at localhost:3000/portfolio/portfolioId it will redirect me back to my homepage. I am not sure what is going on. I have also tried manipulating the URL by modifying it to the correct URL link but it also redirects me back to /homepage when I am authenticated. The source codes can be found below. App.js is my router with PrivateRoute as the private route component and finally, CreateInterview.js where I redirect using js windows.location function to ViewPortfolio.js which will use useParams() react hook to get the param. But instead now after creating successfully and redirect to the correct URL with the portfolioId it will redirect back to homepage within less than a second.

PrivateRoute.js

import React from 'react' 
import { Route, Redirect } from 'react-router-dom' 
import { useAuth } from '../contexts/AuthContext' 
 
const PrivateRoute = ({ component: Component, ...rest }) => { 
  const { currentUser } = useAuth() 
 
  return ( 
    <Route 
      {...rest} 
      render={(props) => { 
        if (currentUser) { 
          return <Component {...props} /> 
        } else { 
          return <Redirect to={{ 
            pathname: "/", 
            state:{ 
              from: props.location 
            } 
          }}/> 
        } 
      } 
      }> 
 
    </Route> 
  ) 
} 
 
export default PrivateRoute

App.js

import React from "react" 
.
.
.
import PublicRoute from "./PublicRoute"; 
 
function App() { 
    return ( 
        <AuthProvider> 
            <Router> 
                <Switch> 
                    {/* Auth Routes */} 
                    <PublicRoute exact path='/' component={Login} /> 
                    .
                    .
                    .
                    <PrivateRoute exact path='/createInterview' component={CreateInterview} /> 
                    <PrivateRoute path='/manageInterview' component={ManageInterview} /> 
                    <PrivateRoute path='/portfolio/:portfolioId' component={ViewPortfolio} /> 
 
                    {/* Non-Existance Routes */} 
                    <Route path="*" component={() => "404 NOT FOUND"} /> 
                </Switch> 
            </Router> 
        </AuthProvider> 
    ) 
} 
 
export default App

CreatInterview.js redirecting in js (onSubmit of the form)

async function handleSubmit(e) { 
    e.preventDefault(); 
    setError(''); 
    setLoading(true); 
 
    await database.portfolioRef.add({ 
      intervieweeName: intervieweeNameRef.current.value, 
      intervieweeEmail: intervieweeEmailRef.current.value, 
      intervieweeMobileNumber: intervieweeMobileRef.current.value, 
      projectTitle: projectTitleRef.current.value, 
      portfolioTitle: portfolioNameRef.current.value, 
      dateCreated: new Date().toLocaleString('en-SG'), 
      createdBy: currentUser.displayName 
    }).then(function(docRef) { 
      console.log("This is the Document ID "   docRef.id.toString()); 
      console.log(docRef.id); 
      window.location = '/portfolio/'   docRef.id; 
    }) 
 
    setLoading(false) 
  }

Part of ViewPortfolio.js to receive the portfolioId from CreateInterview.js

const ViewPortfolio = () => { 
    let { portfolioId } = useParams(); 

AuthContext.js

import React, { useContext, useState, useEffect } from "react" 
import { auth, database } from "../firebase"; 
import { getDocs, query, where } from "firebase/firestore"; 
 
const AuthContext = React.createContext() 
 
export function useAuth() { 
    return useContext(AuthContext) 
} 
 
export function AuthProvider({ children }) { 
    const [currentUser, setCurrentUser] = useState(null) 
    const [loading, setLoading] = useState(true) 
 
    function login(email, password) { 
        return auth.signInWithEmailAndPassword(email, password).then(() => { 
            const Doc = query(database.usersRef, where("email", "==", email)); 
 
            getDocs(Doc).then((querySnapshot) => { 
                let values = ''; 
 
                querySnapshot.forEach((doc) => { 
                    values = doc.id; 
                }); 
 
                var userUpdate = database.usersRef.doc(values); 
                userUpdate.update({ 
                    lastActive: new Date().toLocaleString('en-SG'), 
                }) 
            }) 
        }); 
    } 
 
    function logout() { 
        return auth.signOut(); 
    } 
 
    function forgetPassword(email) { 
        return auth.sendPasswordResetEmail(email); 
    } 
 
    function updateEmail(email) { 
        return currentUser.updateEmail(email) 
    } 
 
    function updatePassword(password) { 
        return currentUser.updatePassword(password) 
    } 
 
    function updateDisplayName(name) { 
        return currentUser.updateDisplayName(name) 
    } 
 
    useEffect(() => { 
        const unsubscribe = auth.onAuthStateChanged( user => { 
            setLoading(false) 
            setCurrentUser(user) 
        }) 
 
        return unsubscribe 
    }, []) 
 
    const value = { 
        currentUser, 
        login, 
        forgetPassword, 
        logout, 
        updateEmail, 
        updatePassword, 
        updateDisplayName, 
    } 
 
    return ( 
        <AuthContext.Provider value={value}> 
            {!loading && children} 
        </AuthContext.Provider> 
    ) 
}

CodePudding user response:

The initial currentUser state matches the unauthenticated state, so when the app initially renders, if you are accessing a protected route the redirection will occur because the currentUser state hasn't updated yet.

Since onAuthStateChanged returns null for unauthenticated users then I suggest using anything other than null for the initial currentUser state. undefined is a good indeterminant value. You can use this indeterminant value to conditionally render a loading indicator, or nothing at all, while the auth status is confirmed on the initial render.

AuthProvider

export function AuthProvider({ children }) { 
  const [currentUser, setCurrentUser] = useState(); // <-- undefined
  ...

PrivateRoute

const PrivateRoute = (props) => { 
  const { currentUser } = useAuth();

  if (currentUser === undefined) {
    return null; // or loading spinner, etc...
  }

  return currentUser
    ? (
      <Route {...props} />
    )
    : (
      <Redirect
        to={{ 
          pathname: "/", 
          state: { 
            from: props.location 
          } 
        }}
      />
    );
}

You should also really replace the window.location = '/portfolio/' docRef.id; logic with a history.push('/portfolio/' docRef.id); so you are not unnecessarily reloading the page.

const history = useHistory();

...

async function handleSubmit(e) { 
  e.preventDefault(); 
  setError(''); 
  setLoading(true); 

  try {
    const docRef = await database.portfolioRef.add({ 
      intervieweeName: intervieweeNameRef.current.value, 
      intervieweeEmail: intervieweeEmailRef.current.value, 
      intervieweeMobileNumber: intervieweeMobileRef.current.value, 
      projectTitle: projectTitleRef.current.value, 
      portfolioTitle: portfolioNameRef.current.value, 
      dateCreated: new Date().toLocaleString('en-SG'), 
      createdBy: currentUser.displayName 
    });
     
    history.push('/portfolio/'   docRef.id);
  } catch (error) {
    // handle error, clear loading state
    setLoading(false);
  }
}
  • Related