Navigate from app.js, that is outside of the BrowserRouter wrapping

32 Views Asked by At

app.js

import React from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import configureStore from "./store/configureStore";
import { startSetExpenses } from "./actions/expenses";
import 'normalize.css/normalize.css';
import './styles/styles.scss';
import AppRouter from "./routers/AppRouter";
import 'react-dates/lib/css/_datepicker.css';
import { auth } from './firebase/firebase';
import { onAuthStateChanged } from "firebase/auth";
import { useNavigate } from "react-router-dom";

const root = createRoot(document.getElementById("app"));

const navigate = useNavigate();

const store = configureStore();

const jsx = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

const loadingComponent = <div><p>Loading...</p></div>;

root.render(loadingComponent);

store.dispatch(startSetExpenses())
.then(() => {
    root.render(jsx);
})
.catch((error) => {
    console.error("Error fetching expenses:", error);
});

onAuthStateChanged(auth, (user) => {
    if(user){
        console.log("LoggedIN");
        navigate("/dashboard");
    }else{
        console.log("LoggedOUT");
        navigate("/");
    }
})

AppRouter.js:

import React from 'react';
import LoginPage from '../components/LoginPage';
import Header from '../components/Header';
import NotFound from '../components/NotFound';
import EditExpense from '../components/EditExpense';
import AddExpense from '../components/AddExpense';
import ExpenseDashboardPage from '../components/ExpenseDashboard';
import HelpPage from '../components/HelpPage';
import { Routes, Route, BrowserRouter } from 'react-router-dom';


const AppRouter = () => (
    <BrowserRouter >
            <Header/>
            <Routes >
                <Route path="/" element={<LoginPage />} />
                <Route path="/dashboard" element={<ExpenseDashboardPage />} />
                <Route path="/create" element={<AddExpense />} />
                <Route path="/edit/:id" element={<EditExpense />} />
                <Route path="/help" element={<HelpPage />} />
                <Route path="*" element={<NotFound />} /> 
            </Routes>
    </BrowserRouter>
);

export default AppRouter;

The store.dispatch(startSetExpenses()) is to fetch the initial data from firebase. Till it gets resolved, I am rendering the loadingComponent on screen. What I expected by using the useNavigate from react-router-dom was to navigate to the "/" path when user is not logged in, in the onAuthStateChanged, and to "/dashboard" when a user is logged in. But it isn't working. How do I achieve that?

2

There are 2 best solutions below

0
Drew Reese On BEST ANSWER

You cannot call React hooks outside React components or custom React hooks. I suggest moving the useNavigate hook call and auth state change listener into AppRouter and hoisting the BrowserRouter to the index file to wrap AppRouter to provide it the routing context.

Example:

App:

import React from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import 'react-dates/lib/css/_datepicker.css';
import configureStore from "./store/configureStore";
import 'normalize.css/normalize.css';
import { startSetExpenses } from "./actions/expenses";
import './styles/styles.scss';
import AppRouter from "./routers/AppRouter";
import { auth } from './firebase/firebase';

const root = createRoot(document.getElementById("app"));

const store = configureStore();

const jsx = (
  <Provider store={store}>
    <BrowserRouter>
      <AppRouter />
    </BrowserRouter>
  </Provider>
);

const loadingComponent = <div><p>Loading...</p></div>;

root.render(loadingComponent);

store.dispatch(startSetExpenses())
  .then(() => {
    root.render(jsx);
  })
  .catch((error) => {
    console.error("Error fetching expenses:", error);
  });

AppRouter:

import React from 'react';
import { Routes, Route, useNavigate } from 'react-router-dom';
import { onAuthStateChanged } from "firebase/auth";
import LoginPage from '../components/LoginPage';
import Header from '../components/Header';
import NotFound from '../components/NotFound';
import EditExpense from '../components/EditExpense';
import AddExpense from '../components/AddExpense';
import ExpenseDashboardPage from '../components/ExpenseDashboard';
import HelpPage from '../components/HelpPage';

const AppRouter = () => {
  const navigate = useNavigate();

  React.useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        console.log("LoggedIN");
        navigate("/dashboard");
      } else {
        console.log("LoggedOUT");
        navigate("/");
      }
    });

    return unsubscribe;
  }, []);

  return (
    <>
      <Header />
      <Routes>
        <Route path="/" element={<LoginPage />} />
        <Route path="/dashboard" element={<ExpenseDashboardPage />} />
        <Route path="/create" element={<AddExpense />} />
        <Route path="/edit/:id" element={<EditExpense />} />
        <Route path="/help" element={<HelpPage />} />
        <Route path="*" element={<NotFound />} /> 
      </Routes>
    </>
  );
};

export default AppRouter;
1
Mycolaos On

You should debug. Is onAuthStateChanged ever called? Maybe it's not running?

useNavigate is a hook, it must be used inside a component.