1

Background

I am building a web app with a Reactjs and Firebase backend / auth. I have setup and AuthContextProvider and using React Router to navigate.

Problem

None of the routes are resolving.

Code

// App.js
import React from "react";
import {
  BrowserRouter as Router,
  Route,
  Link,
  Redirect,
} from "react-router-dom";
import { AuthContextProvider, useAuthState } from "./firebase";

import Navigation from "./components/layout/Navigation";
import Landing from "./components/screens/Landing";
import SignUp from "./components/screens/SignUp";
import SignIn from "./components/screens/SignIn";
import ForgotPassword from "./components/screens/ForgotPassword";
import Home from "./components/screens/Home";
import Profile from "./components/screens/Profile";
import Account from "./components/screens/Account";
import Admin from "./components/screens/Admin";

import * as ROUTES from "./constants/routes";

const AuthenticatedRoute = ({ component: C, ...props }) => {
  const { isAuthenticated } = useAuthState();
  console.log(`AuthenticatedRoute: ${isAuthenticated}`);
  return (
    <Route
      {...props}
      render={(routeProps) =>
        isAuthenticated ? <C {...routeProps} /> : <Redirect to="/login" />
      }
    />
  );
};
const UnauthenticatedRoute = ({ component: C, ...props }) => {
  const { isAuthenticated } = useAuthState();
  console.log(`UnauthenticatedRoute: ${isAuthenticated}`);
  return (
    <Route
      {...props}
      render={(routeProps) =>
        !isAuthenticated ? <C {...routeProps} /> : <Redirect to="/home" />
      }
    />
  );
};

const App = () => (
  <AuthContextProvider>
    <Router>
      <Navigation />
      <UnauthenticatedRoute exact path={ROUTES.SIGN_UP} component={SignUp} />
      <UnauthenticatedRoute exact path={ROUTES.SIGN_IN} component={SignIn} />
      <UnauthenticatedRoute
        exact
        path={ROUTES.PASSWORD_RESET}
        component={ForgotPassword}
      />
      <UnauthenticatedRoute exact path={ROUTES.LANDING} component={Landing} />

      <AuthenticatedRoute exact path={ROUTES.ACCOUNT} component={Account} />
      <AuthenticatedRoute exact path={ROUTES.ADMIN} component={Admin} />
      <AuthenticatedRoute exact path={ROUTES.PROFILE} component={Profile} />
      <AuthenticatedRoute exact path={ROUTES.HOME} component={Home} />
    </Router>
  </AuthContextProvider>
);

export default App;
// firebase.js
import { getAuth, onAuthStateChanged } from "@firebase/auth";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { useState, useEffect, useContext, createContext } from "react";

const firebaseConfig = {
  ...
};

export const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore();

export const AuthContext = createContext();

export const AuthContextProvider = (props) => {
  const [user, setUser] = useState();
  const [error, setError] = useState();

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(getAuth(), setUser, setError);
    return () => unsubscribe();
  }, []);
  return <AuthContext.Provider value={{ user, error }} {...props} />;
};

export const useAuthState = () => {
  const auth = useContext(AuthContext);
  return { ...auth, isAuthenticated: auth.user != null };
};
// routes.js
export const LANDING = "/";
export const SIGN_UP = "/signup";
export const SIGN_IN = "/login";
export const HOME = "/home";
export const ACCOUNT = "/account";
export const ADMIN = "/admin";
export const PASSWORD_RESET = "/forgot-password";
export const PROFILE = "/profile";
// Navigation.js
import React from "react";
import { Link } from "react-router-dom";
import { getAuth, signOut } from "firebase/auth";
import { useAuthState } from "../../firebase";

import * as ROUTES from "../../constants/routes";

import Navbar from "react-bootstrap/Navbar";
import Container from "react-bootstrap/Container";
import Nav from "react-bootstrap/Nav";
import NavDropdown from "react-bootstrap/NavDropdown";

const Navigation = () => {
  const { user } = useAuthState();
  return (
    <div className="navigation">
      <Navbar bg="light" expand="lg">
        <Container>
          <Link to={ROUTES.HOME}>
            <Navbar.Brand>Underground</Navbar.Brand>
          </Link>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="me-auto">
              {user ? (
                <NavDropdown title={user?.email} id="basic-nav-dropdown">
                  <Link to={ROUTES.ACCOUNT}>
                    <NavDropdown.Item>Account</NavDropdown.Item>
                  </Link>
                  <NavDropdown.Divider />
                  <NavDropdown.Item onClick={() => signOut(getAuth())}>
                    Logout
                  </NavDropdown.Item>
                </NavDropdown>
              ) : (
                <NavDropdown title="Users" id="basic-nav-dropdown">
                  <Link to={ROUTES.SIGN_IN}>
                    <NavDropdown.Item>Sign in</NavDropdown.Item>
                  </Link>
                  <Link to={ROUTES.SIGN_UP}>
                    <NavDropdown.Item>Signup</NavDropdown.Item>
                  </Link>
                </NavDropdown>
              )}
            </Nav>
          </Navbar.Collapse>
        </Container>
      </Navbar>
    </div>
  );
};

export default Navigation;

So why not resolving?

I don't think my router has anything configured wrong so why would the routes not resolve? They either resolve to /home for authenticated users, or /login for non-authenticated users, which suggests something to do with redirects?

https://codesandbox.io/s/nostalgic-resonance-uptre

Anthony
  • 317
  • 1
  • 5
  • 23

2 Answers2

0

You need to wrap your Route components with a Switch component eg.

<Router>
<Link></Link>
<Link></Link>
<Switch>
  <Route/>
  <Route/>
</Switch>
<Router/>

See the official guide

prismo
  • 1,505
  • 2
  • 12
  • 26
  • Ah yes, I have now added a Switch component, but that hasn't fixed the issue. It still behaves the same as before. – Anthony Sep 29 '21 at 17:47
  • @Anthony would it be possible for you to create a codesandbox and link it here? – prismo Sep 29 '21 at 18:20
  • Sandbox is at https://codesandbox.io/s/nostalgic-resonance-uptre - Firebase config is fetched from env file (not in the sandbox). – Anthony Sep 29 '21 at 19:24
0

I managed to solve the problem. The issue was not to do with React router not working, it was down to the react-bootstrap syntax being used. I'm not sure what was causing the clash but the following solution has fixed it.

I found the answer here: ReactJS Bootstrap Navbar and Routing not working together

In short, you don't need to insert <Link to="">...</Link> inside <Nav.Link>...</Nav.Link>, you simply add as={Link} to={...} inside <Nav.Link>, or any other component if you wish.

Anthony
  • 317
  • 1
  • 5
  • 23