React main page is not loading - reactjs

I'm trying to develop an online shop in React. Trying to load app.js. I don't get any errors, just a blank page.
App.js as follows:
import AppRouter from "./components/AppRouter";
import NavBar from "./components/NavBar";
const App = observer(() => {
const {user} = useContext(Context)
const [loading, setLoading] = useState(true)
useEffect(() => {
check().then(data => {
user.setUser(true)
user.setIsAuth(true)
}).finally(() => setLoading(false))
}, [])
if (loading) {
return <Spinner animation={"grow"}/>
}
return (
<BrowserRouter>
<NavBar />
<AppRouter />
</BrowserRouter>
);
});
export default App;
NavBar.js as follows:
import {NavLink, useNavigate} from "react-router-dom";
import {ADMIN_ROUTE, LOGIN_ROUTE, SHOP_ROUTE} from "../utils/consts";
const NavBar = observer(() => {
const {user} = useContext(Context)
const navigate = useNavigate()
const logOut = () => {
user.setUser({})
user.setIsAuth(false)
}
return (
<Navbar bg="dark" variant="dark">
<Container>
<NavLink style={{color:'white'}} onClick={SHOP_ROUTE}>Victoria's Tech Store
</NavLink>
{user.isAuth ?
<Nav className="ml-auto" style={{color: 'white'}}>
<Button
variant={"outline-light"}
onClick={() => navigate.push(ADMIN_ROUTE)}
>
Admin
</Button>
<Button
variant={"outline-light"}
onClick={() => logOut()}
className="ml-2"
>
Logout
</Button>
</Nav>
:
<Nav className="ml-auto" style={{color: 'white'}}>
<Button variant={"outline-light"} onClick={() => (LOGIN_ROUTE)}>Login</Button>
</Nav>
}
</Container>
</Navbar>
);
});
export default NavBar;
AppRouter.js as follows:
import React, {useContext} from 'react';
import {BrowserRouter as Routes, Route, Router, useNavigate} from 'react-router-dom'
import {SHOP_ROUTE} from "../utils/consts";
const AppRouter = observer(() => {
const {user} = useContext(Context);
const Navigate = useNavigate();
console.log(user)
return (
<Router>
<Routes>
{user.isAuth && authRoutes.map(({path, Element}) =>
<Route key={path} path={path} element={Element} exact/>
)}
{publicRoutes.map(({path, Element}) =>
<Route key={path} path={path} element={Element} exact/>
)}
<Navigate to={SHOP_ROUTE}/>
</Routes>
</Router>
);
});
export default AppRouter;

Look in the browser console for any error messages, maybe what's causing the issue is some content that isn't being loaded like a file not imported or something along those lines.

Related

React Update Navbar When User Updates Profile

I have a React Application which just contains Authtentication Functionality.
I created a dashboard as a home page which displays users email, and a navbar which checks if user is logged in or not. If user logged in, it displays a dropdown with a title user's email(dropdown items: update profile/log out) else, it just displays a log in button.
In the Update Profile page, User can change password and email.
My problem is, if user chanhges email, navbar do not update itself but dashboard do.
App.js:
import "./style.css";
import Navigation from "./Navbar";
import Signup from "./Signup";
import { AuthProvider } from "../contexts/AuthContext";
import Dashboard from "./Dashboard";
import Login from "./Login";
import Logout from "./Logout";
import ForgotPassword from "./ForgotPassword";
import PrivateRoute from "./PrivateRoute";
import UpdateProfile from "./UpdateProfile";
function App() {
return (
<Router >
<AuthProvider>
<Navigation />
<Routes>
<Route
path="/"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
></Route>
<Route
path="/update-profile"
element={
<PrivateRoute>
<UpdateProfile />
</PrivateRoute>
}
></Route>
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/logout" element={<Logout />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
</Routes>
</AuthProvider>
</Router>
);
}
export default App;
Navbar.js:
import Container from "react-bootstrap/Container";
import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar";
import NavDropdown from "react-bootstrap/NavDropdown";
import { useAuth } from "../contexts/AuthContext";
import { useEffect, useState } from "react";
const Navigation = () => {
const { currentUser } = useAuth();
const [email, setEmail] = useState("");
return (
<div>
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand>
<Nav.Link as={Link} to="/">
Commercial
</Nav.Link>
</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav>
<NavDropdown
title="Man"
className="dropdown"
id="basic-nav-dropdown"
renderMenuOnMount={true}
>
<NavDropdown.Item>Shirt</NavDropdown.Item>
<NavDropdown.Item>Jean</NavDropdown.Item>
</NavDropdown>
<NavDropdown
title="Woman"
id="basic-nav-dropdown"
className="dropdown"
renderMenuOnMount={true}
>
<NavDropdown.Item>Dress</NavDropdown.Item>
<NavDropdown.Item>Skirt</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
<Navbar.Collapse
className="justify-content-end"
id="responsive-navbar-nav"
>
<Nav>
{currentUser ? (
<NavDropdown
title={currentUser.email}
className="dropdown"
id="basic-nav-dropdown"
renderMenuOnMount={true}>
<Nav.Link as={Link} to="/update-profile">
Go Profile
</Nav.Link>
<Nav.Link as={Link} to="/logout">
Log Out
</Nav.Link>
</NavDropdown>
) : (
<Nav.Link as={Link} to="/login">
Login
</Nav.Link>
)}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
);
};
export default Navigation;
Dashboard.js:
import { Card, Button, Alert, Container } from "react-bootstrap";
import { useAuth } from "../contexts/AuthContext";
const Dashboard = () => {
const { currentUser } = useAuth();
return (
<Container className="mt-5 d-flex align-items-center justify-content-center">
<div className="w-100" style={{ maxWidth: "400px" }}>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Profile</h2>
<strong>Email</strong> {currentUser.email}
</Card.Body>
</Card>
</div>
</Container>
);
};
export default Dashboard;
I think I should re-render the navbar every time the page is loaded.
How can I do that, or is there another solutions?
PS:I have not any functional error/bug. Everything works fine.
EDIT: I am sharing other codes, may be this can help:
AuthContext.js:
import React, { useContext, useState, useEffect } from "react";
import { auth } from "../firebase";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password)
}
const logout = () => {
return auth.signOut();
}
const resetPassword = (email) => {
return auth.sendPasswordResetEmail(email);
}
const updateEmail = (email) => {
return currentUser.updateEmail(email);
}
const updatePassword = (password) => {
return currentUser.updatePassword(password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword
};
return (
<AuthContext.Provider value={value}>
<div></div>
{!loading && children}
</AuthContext.Provider>
);
}
PrivateRoute.js:
import { Navigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
const PrivateRoute = ({ children }) => {
const { currentUser } = useAuth();
return currentUser ? children : <Navigate to="/login" />;
};
export default PrivateRoute;
UpdateProfile.js:
import { Form, Button, Card, Container, Alert } from "react-bootstrap";
import { useAuth } from "../contexts/AuthContext";
import { Link, useNavigate } from "react-router-dom";
const UpdateProfile = () => {
const emailRef = useRef();
const passwordRef = useRef();
const passwordConfirmRef = useRef();
const { currentUser, updateEmail, updatePassword } = useAuth();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const navigator = useNavigate();
async function handleSubmit(e) {
e.preventDefault();
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match");
}
const promises = [];
setLoading(true);
if(emailRef.current.value !== currentUser.email){
promises.push(updateEmail(emailRef.current.value));
}
if(passwordRef.current.value){
promises.push(updatePassword(passwordRef.current.value));
}
Promise.all(promises).then(() => {
navigator("/");
}).catch(() =>{
setError("Failed to update Account");
}).finally(() =>{
setLoading(false);
})
}
return (
<Container className="mt-5 d-flex align-items-center justify-content-center">
<div className="w-100" style={{ maxWidth: "400px" }}>
{error && <Alert variant="danger">{error}</Alert>}
<Card>
<Card.Body>
<h2 className="text-center mb-4">Update Profile</h2>
<Form onSubmit={handleSubmit}>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
ref={emailRef}
defaultValue={currentUser.email}
required
/>
</Form.Group>
<Form.Group id="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
ref={passwordRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<Form.Group id="passwordConfirm">
<Form.Label>Password Confirmation</Form.Label>
<Form.Control
type="password"
ref={passwordConfirmRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<Button disabled={loading} type="submit" className="w-100 mt-3">
Update
</Button>
</Form>
</Card.Body>
</Card>
<div className="w-100 text-center mt-2">
<Link to="/" style={{ textDecoration: "none" }}>
Cancel
</Link>
</div>
</div>
</Container>
);
};
export default UpdateProfile;
I think your Navbar "Navigation" Component is not re-rendering on route change.
You could use redux or context api for state management so that when email updates it is updated in the redux or context api store.
So, your Navbar state should update as it will be taking its value from the redux or context api store and if that value changes the state will update in turn the function will re-render
I am not sure if there are other solutions to this.
From what I can see here it looks like the currentUser object reference is possibly mutated by Firebase. In other words, the currentUser reference in the AuthProvider component only updates when the authentication status changes, but not when user properties like their email are updated.
Since it seems that when changing routes the Dashboard component is rerendered and "sees" the updated currentUser.email value you could create a layout route component that renders the Navigation component.
Example:
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
const PrivateRoute = () => {
const { currentUser } = useAuth();
return currentUser ? <Outlet /> : <Navigate to="/login" replace />;
};
...
import { Outlet } from "react-router-dom";
export const Layout = () => (
<>
<Navigation />
<Outlet />
</>
);
function App() {
return (
<Router >
<AuthProvider>
<Routes>
<Route element={<Layout />}> // <-- Navbar renders with routes
<Route element={<PrivateRoute />}>
<Route path="/" element={<Dashboard />} />
<Route path="/update-profile" element={<UpdateProfile />} />
</Route>
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/logout" element={<Logout />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
</Route>
</Routes>
</AuthProvider>
</Router>
);
}
An alternative might be to add an additional "state" to the AuthProvider component for marking "updates" to trigger rerenders.
Example:
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const [update, setUpdate] = useState(0);
...
const updateEmail = async (email) => {
const result = await currentUser.updateEmail(email);
setUpdate(c => c + 1);
return result;
}
const updatePassword = async (password) => {
const result = await currentUser.updatePassword(password);
setUpdate(c => c + 1);
return result;
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}

ReferenceError: Worker is not defined while testing AppRouter Component

I am testing a React/Typescript component using Jest and React Testing-Library. I am doing a simple render test and getting a Reference Error: Worker is not defined.
Why and how would I use a worker in this testing context?
Here is my simple test:
import {render, screen} from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import React from 'react'
import {Router} from 'react-router-dom'
import AppRouter from '../router'
test('AppRouter renders all routes and I can navigate to those pages', () => {
render(<AppRouter />)
screen.debug()
})
And here is the AppRouter component:
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import { useState } from 'react'
import useLocalStorage from './hooks/useLocalStorage'
import * as Constants from './constants'
import Header from './layout/header/header'
import MainPage from './pages/mainPage/mainPage'
import PostPage from './pages/postPage/postPage'
import UserPage from './pages/userPage/userPage'
import LoginPage from './pages/loginPage/loginPage'
import SignupPage from './pages/signupPage/signupPage'
import NewPage from './pages/newPage/newPage'
import FeedbackPage from './pages/feedbackPage/feedbackPage'
import AdminPage from './pages/adminPage/adminPage'
import SettingPage from './pages/settingPage/settingPage'
import { WebContext } from './context/WebContext'
import Favicon from 'react-favicon'
const AppRouter = () => {
const [adminCode, setAdminCode] = useLocalStorage('admin', '')
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [page, setPage] = useState(Constants.Page.Home)
return (
<BrowserRouter>
<div>
<Favicon url={require('../public/favicon.ico')} />
<WebContext.Provider
value={{
isMenuOpen,
setIsMenuOpen,
page,
setPage,
adminCode,
setAdminCode,
}}
>
<Header />
<Switch>
<Route component={MainPage} path="/" exact={true} />
<Route component={PostPage} path="/post/:id" />
<Route component={UserPage} path="/user" />
<Route component={LoginPage} path="/login" />
<Route component={SignupPage} path="/signup" />
<Route component={NewPage} path="/new" />
<Route component={FeedbackPage} path="/feedback" />
<Route component={AdminPage} path="/admin" />
<Route component={SettingPage} path="/setting" />
<Route component={() => <Redirect to="/" />} />
</Switch>
</WebContext.Provider>
</div>
</BrowserRouter>
)
}
export default AppRouter
Most of what I researched on this were old Jest stackoverflows. I am aware there is a jest-worker package but not sure why I would need this or how I would use it when running this simple test.
Here is a link to jest-worker.
Code Trace:
Header component
import { useContext, useState } from 'react'
import { NavLink, useHistory, useLocation } from 'react-router-dom'
import { observer } from 'mobx-react-lite'
import { WebContext } from '../../context/WebContext'
import UnirepContext from '../../context/Unirep'
import UserContext from '../../context/User'
const Header = () => {
const history = useHistory()
const location = useLocation()
const { isMenuOpen, setIsMenuOpen } = useContext(WebContext)
const [searchInput, setSearchInput] = useState<string>('')
const unirepConfig = useContext(UnirepContext)
const userContext = useContext(UserContext)
const gotoNewPage = () => {
if (
userContext.userState &&
userContext.netReputation >= unirepConfig.postReputation
) {
history.push(`/new`, { isConfirmed: true })
}
}
const gotoUserPage = () => {
history.push(`/user`, { isConfirmed: true })
}
const openMenu = () => {
if (!isMenuOpen) {
console.log('open menu!')
setIsMenuOpen(true)
}
}
const handleSearchInput = (event: any) => {
console.log('search input : ' + event.target.value)
}
return (
<header>
<div className="navLinks">
<NavLink to="/" className="link" activeClassName="active" exact>
<img
src={require('../../../public/images/unirep-title.svg')}
/>
</NavLink>
</div>
{/* <div className="search-bar">
<div className="search-icon"><FaSearch /></div>
<form>
<input type="text" name="searchInput" placeholder="Search by keyword, user names or epoch key" onChange={handleSearchInput} />
</form>
</div> */}
{userContext.userState ? (
<div className="navButtons">
<div id="rep" onClick={gotoUserPage}>
<img
src={require('../../../public/images/lighting.svg')}
/>
{userContext.netReputation}
</div>
<div
id="new"
className={
location.pathname === '/new'
? 'navBtn chosen'
: 'navBtn'
}
>
<img
src={require('../../../public/images/newpost.svg')}
onClick={gotoNewPage}
/>
</div>
<div
id="user"
className={
location.pathname === '/user'
? 'navBtn chosen'
: 'navBtn'
}
>
<img
src={require('../../../public/images/user.svg')}
onClick={gotoUserPage}
/>
</div>
<div className="navBtn">
<img
src={require('../../../public/images/menu.svg')}
onClick={openMenu}
/>
</div>
</div>
) : (
<div className="navButtons">
<div
id="login"
className="whiteButton"
onClick={() => history.push('/login')}
>
Sign in
</div>
<div
id="join"
className="blackButton"
onClick={() => history.push('/signup')}
>
Join
</div>
<div id="menu" className="navBtn">
<img
src={require('../../../public/images/menu.svg')}
onClick={openMenu}
/>
</div>
</div>
)}
</header>
)
}
export default observer(Header)
EDIT
The problem may stem from using MobX for state management and not wrapping the component in a Provider but still unsure of how to do this.

Github API Not displaying users

Having trouble displaying a list of Github usernames and profile pics.
I'm using https://api.github.com/users to fetch the data from the API. I haven't started working on the profile links yet.
Here's what's being displayed
Here's my App.js:
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
import Navbar from './components/layout/Navbar';
import Footer from './components/layout/Footer';
function App() {
return (
<Router>
<div className='flex flex-col justify-between h-screen'>
<Navbar />
<main className='container mx-auto px-3 pb-12'>
<Routes>
<Route path='/' element={< Home />} />
<Route path='/about' element={< About />} />
<Route path='/notfound' element={< NotFound />} />
<Route path='/*' element={< NotFound />} />
</Routes>
</main>
<Footer />
</div>
</Router>
);
}
export default App;
Here's UserResults.jsx:
import {useEffect, useState} from 'react'
import Spinner from '../layout/Spinner'
import UserItem from './UserItem'
function UserResults() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchUsers()
}, [users])
const fetchUsers = async () => {
const response = await fetch(`https://api.github.com/users`)
const data = await response.json()
setUsers(data)
setLoading(false)
}
if (!loading) {
return (
<div className='grid grid-cols-1 gap-8 xl:grid-cols-4
lg:grid-cols-3 md:grid-cols-2'>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</div>
)
} else {
return <h3><Spinner /></h3>
}
}
export default UserResults
UserItem.jsx which displays the user info:
import { Link } from 'react-router-dom'
import PropTypes from 'prop-types'
function UserItem({users: login, avatar_url}) {
return (
<div className='card shdow-md compact bg-base-100 '>
<div className='flex-row items-center space-x-4 card-body'>
<div>
<div className='avatar'>
<div className='rounded-full shadow w-14 h-14'>
<img src={avatar_url} alt='profile pic'></img>
</div>
</div>
</div>
<div>
<h2 className='card-title'>{login}</h2>
<Link className='text-base-content text-opacity-40' to={`/users/${login}`}>
Visit Profile
</Link>
</div>
</div>
</div>
)
}
UserItem.propTypes = {
users: PropTypes.object.isRequired,
}
export default UserItem
Feel like I'm missing something with the way I reference the API.
Thank you in advance!
The UserItem component is being called as:
<UserItem key={user.id} user={user} />
You should define it as follows:
function UserItem({key, user}) {
// here you can use user.avatar_url and any other user related key
Seems that object destructuring in UserItem function is written wrong:
function UserItem({users: { login, avatar_url }}) {

Problem when reloading page using react-router-dom

I managed to make a private route and navigate to different pages using react-router-dom. How ever, when I navigate to a page and reload it, it first goes to /login for half a second and the reloads the page correctly. How can I prevent this unwanted behavior and improve my routing?
Here are my routes:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
This is the full component:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
This is my Login component.
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "#material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
This is the App component:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "#material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
Also, here is the auth logic:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
Finally, the full code is in https://gitlab.com/programandoconro/adminkanjicreator
Any suggestion will be appreciated, thanks.
I would recommend doing the auth check earlier. So something like this so that the routes themselves only get rendered if there is something in auth. I think your example is also missing the Switch statement which often helps.
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
Typically you will want some sort of "loading" or "indeterminant" state to represent neither authenticated nor unauthenticated. You can use this third "state" to hold the UI before committing to rendering one way or the other on anything based upon authentication.
Since your auth logic resolves to a boolean true|false.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
You can use the fact that the initial auth state is neither of these. I suggest using null.
const [auth, setAuth] = useState<string | null>(null);
When rendering the Route utilizing the auth state you can augment the logic to return early before deciding to redirect.
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
Note here that I've also switched over to the render prop, the component prop is intended for attaching actual React components. These are treated a little differently. You can read about the route render method differences here.
The full router example:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
Note here that I've also included the Switch component and reordered the routes so the more specific paths are listed before less specific paths. This allows you to remove the unnecessary exact prop from all the routes since the Switch renders routes exclusively (versus inclusively as the Router does).
I finally managed to solve the issue. Now the reload works perfectly and the security was implemented as excepted. This is my final Router:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
This is how the component looks like now.
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
Thanks #Richard and #Drew for their kind support.

React and antd: Router doesn't re-render components

I have a simple web page with login and search pages. I also have a navbar at the top to allow for switching between the two. The basic App.js looks as follows:
const history = createBrowserHistory();
function App() {
return (
<Router history={history}>
<CustomLayout>
<Switch>
<BaseRouter/>
</Switch>
</CustomLayout>
</Router>
);
}
export default App;
Now, the BaseRouter and CustomLayout are just
const BaseRouter = () => (
<div>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</div>
);
export default BaseRouter;
and
const CustomLayout = ({children}) => {
return(
<>
<Navbar/>
{children}
</>
);
}
export default CustomLayout;
Now, the navbar looks like this
import React from "react";
import {Menu} from 'antd';
import {Link} from "react-router-dom";
const Navbar = () => {
return (
<div>
<Menu mode="horizontal" theme={"dark"}>
<Menu.Item key="list">
<Link to={"/list"}>List</Link>
</Menu.Item>
<Menu.Item key={"login"}>
<Link to={"/login"}>Sign in</Link>
</Menu.Item>
</Menu>
</div>
);
}
export default Navbar
Let's keep components simple:
const Login = () => {
return (
<div>
login
</div>
);
}
export default Login
const List = () => {
return (
<div>
list
</div>
);
}
export default List
Now the problem is that when I click on links in the navbar, React doesn't re-render components even though the route changes. I've seen hundreds of answers on SO but I still can't figure it out.
NOTE
It is important for me to avoid refreshing or reloading the page.
EDIT
Strangely enough, when I change Router to BrowserRotuer it works fine, but I can't use my own history then.
why don't you use BrowserRouter from react-router-dom package.
App.js:- use BrowserRouter from react-router-dom
import { BrowserRouter as Router, Switch } from 'react-router-dom'
function App() {
return (
<Router>
<CustomLayout>
<Switch>
<BaseRouter/>
</Switch>
</CustomLayout>
</Router>
);
}
export default App;
BaseRouter.js:- import Route from react-router-dom
import { Route } from 'react-router-dom'
const BaseRouter = () => (
<div>
<Route exact path="/list" component={ItemsList}/>
<Route path="/login" component={LoginForm}/>
</div>
);
export default BaseRouter;
Navbar.js:-
import React from "react";
import {Menu} from 'antd';
import {Link} from "react-router-dom";
const Navbar = () => {
return (
<div>
<Menu mode="horizontal" theme={"dark"}>
<Menu.Item key={"list"}>
<Link to="/list">List</Link>
</Menu.Item>
<Menu.Item key={"login"}>
<Link to="/login">Sign in</Link>
</Menu.Item>
</Menu>
</div>
);
}
export default Navbar
then if you want to use history:-
import { useHistory } from 'react-router-dom'
const testFunctionComponent = () => {
const history = useHistory()
const handleClick = (urlPath) => {
// you can do
history.push(urlPath) // to go anywhere
}
return (
<>
<button onClick={() => handleClick('/anypath')}>Click Me!<button>
</>
)
}
Change your BaseRouter from this :
const BaseRouter = () => (
<div>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</div>
);
export default BaseRouter;
To this :
const BaseRouter = () => (
<div>
<Route exact path="/list" component={ItemsList}/>
<Route path="/login" component={LoginForm}/>
</div>
);
export default BaseRouter;
I believe you cannot have the div inside the switch. You're not exposing the Route components to your switch statement.
Therefore, your url changes because your Navbar makes it change but your switch doesn't know what to do.
Try changing your base router to this:
const history = createBrowserHistory();
function App() {
return (
<Router history={history}>
<CustomLayout>
<BaseRouter/>
</CustomLayout>
</Router>
);
}
export default App;
const BaseRouter = () => (
<Switch>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</Switch>
);
export default BaseRouter;
Router is low-level and expects you to manage things. BrowserRouter already syncs with HTML5 history. If you want Router, I think you have to manage the syncing yourself (e.g. <Link onClick={() => history.push(href)}>) or listen to history for change detection. See Detect Route Change with react-router

Resources