I have this react code.
Apps.js
// react-router-auth
import React, {useState} from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import PrivateRoute from './PrivateRoute';
import Home from './pages/Home';
import Admin from './pages/Admin';
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import { AuthContext } from "./context/auth";
function App(props) {
const [authTokens, setAuthTokens] = useState();
const setTokens = (data) => {
localStorage.setItem("tokens", JSON.stringify(data));
setAuthTokens(data);
}
return (
<AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
<Router>
<div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to={{ pathname: "/admin", state:{username: props.location}}}>Admin Page</Link>
</li>
<li>
<Link to={{ pathname: "/login", state:{referer: props.location}}}>Login Page</Link>
</li>
<li>
<Link to={{ pathname: "/signup", state:{referer: props.location}}}>Sign Up Page</Link>
</li>
</ul>
<p>
stuff
</p>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<PrivateRoute path="/admin" component={Admin} />
</div>
</Router>
</AuthContext.Provider>);
}
export default App;
Login.js
import React, { useState } from "react";
import { Link, Redirect } from 'react-router-dom';
import axios from 'axios';
import logoImg from "../img/logo.svg";
import { Card, Logo, Form, Input, Button, Error } from '../components/AuthForms';
import { useAuth } from "../context/auth";
function Login(props) {
const [isLoggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [userName, setUserName] = useState("");
const [password, setPassword] = useState("");
const { setAuthTokens } = useAuth();
const referer = props.location.state.referer || '/';
function postLogin() {
console.log('postLogin called.');
const something = axios.post('http://127.0.0.1:8000/rest-auth/login/', {"username" : userName, "password": password} ).then(
result => {
console.log('postLogin called. username is ' + userName);
if( result.status === 200 ) {
setAuthTokens(result.data);
setLoggedIn(true);
setUserName(userName=> userName);
} else {
setIsError(true);
}
}).catch(e=>{setIsError(true);}
);
console.log('postLogin end. something is :');
console.log(something);
} // end postLogin
if (isLoggedIn) {
console.log('postLogin isLoggedIn is true and referer is ' + referer);
return <Redirect to={referer} />;
}
return (
<Card>
<Logo src={logoImg} />
<Form>
<Input
type="username"
value={userName}
onChange={e=>{
setUserName(e.target.value);
}}
placeholder="username"
/>
<Input
type="password"
value={password}
onChange={e=>{
setPassword(e.target.value);
}}
placeholder="password"
/>
<Button onClick={postLogin}>Sign In</Button>
</Form>
<Link to="/signup">Don't have an account?</Link>
{ isError&& <Error>The username or password provider were incorrect.</Error>}
</Card>
);
}
export default Login;
Admin.js
import React, { useState } from "react";
import { Button } from "../components/AuthForms"
import {useAuth} from "../context/auth";
function Admin(props) {
const { setAuthTokens } = useAuth();
const [userName, setUserName] = useState();
function logOut () {
setAuthTokens();
}
return (
<div>
<div>Admin Page userName is [{props.username}]</div>
<div>Admin Page userName is [{userName}]</div>
<Button onClick={logOut}>Log out</Button>
</div>);
}
export default Admin;
When I login I do not see the userName.
What a I doing wrong?
Update: I also have this file, auth.js
import { createContext, useContext } from 'react';
export const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
The problem is that you're not passing the property username to the Admin page. The property is being set in Login page and its not assigned to the context or the Admin page.
One of the easiest ways of setting a logged in Username is setting it in localstorage and retrieving it in the pages you need. This is obviously very exploitable and not recommended for sensitive data.
The other workaround is using React's Context APIs. In your AuthContext you can use useState(username, setUsername) = (window.localStorage.getItem('username') || '') and pass it down as props. You can retrieve the setUsername function in the login page and update it in the admin page.
export const AuthContext = createContext();
const AuthContextProvider = (props) => {
const [username, setUsername] = useState(null);
return (
<AuthContext.Provider
value={{
data: username,
updateUserName: (value) => {
setUsername(value)
}
}}>
{props.children}
</AuthContext.Provider>
);
};
Related
I'm trying to make a private route from my login and signup pages to my dashboard page, but all the tutorials and guides that I've stumbled upon all require some sorta AuthContext thing and I didn't implement my authentication procedure using AuthContext.
I've tried different ways but none of them work and just end up giving me a blank page when I get to the dashboard page, what can I do to make it a private route? Using Firebase v9 btw.
SignUp.js
import './Signup.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { auth }from '../../../firebase';
import { createUserWithEmailAndPassword, onAuthStateChanged } from 'firebase/auth';
import { Typography, Container, TextField, Button, Alert } from '#mui/material';
const Signup = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [user, setUser] = useState({});
const history = useHistory();
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
})
const signup = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
return setError("Passwords do not match")
}
try {
const user = await createUserWithEmailAndPassword(
auth,
email,
password
);
history.push("/dashboard/canvas");
} catch (err) {
setError(err.message);
}
}
return (
<>
<div className="text-div">
<Typography textAlign="center" variant="h3">Create a new account</Typography>
</div>
<Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
{ error && <Alert severity="error">{error}</Alert> }
<TextField label="Email" margin="dense" type="email" onChange={ (e) => {
setEmail(e.target.value);
}}/>
<TextField label="Password" margin="dense" type="password" onChange={ (e) => {
setPassword(e.target.value);
}}/>
<TextField label="Confirm Password" margin="dense" type="password" onChange={ (e) => {
setConfirmPassword(e.target.value);
}}/>
<Button onClick={signup} variant="contained" sx={{ marginTop: 2, }}>Sign Up</Button>
<div>
Already have an account? <Link to="/login" style={{ color: '#000' }}>Log In</Link>
</div>
</Container>
</>
)
}
export default Signup;
Login.js
import './Login.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth }from '../../../firebase';
import { Typography, Container, TextField, Button, Alert } from '#mui/material';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const history = useHistory();
const login = async () => {
try {
const user = await signInWithEmailAndPassword(
auth,
email,
password
);
//alert("Success, user is recognized");
history.push("/dashboard/canvas");
} catch (err) {
setError("The email or password you entered is incorrect");
}
}
return (
<>
<div className="text-div">
<Typography textAlign="center" variant="h3">Login</Typography>
</div>
<Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
{ error && <Alert severity="error">{error}</Alert> }
<TextField label="Email" margin="dense" type="email" onChange={(e) => {
setEmail(e.target.value);
}}/>
<TextField label="Password" margin="dense" type="password" onChange={(e) => {
setPassword(e.target.value);
}}/>
<Button onClick={login} variant="contained" sx={{ marginTop: 2, }}>Login</Button>
<div>
Don't have an account? <Link to="/signup" style={{ color: '#000' }}>Create one here</Link>
</div>
<div>
<Link to="/request-password-reset" style={{ color: '#000' }}>Forgot your password?</Link>
</div>
</Container>
</>
)
}
export default Login;
firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey,
authDomain,
projectId,
storageBucket,
messagingSenderId,
appId,
measurementId
}
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
export {
auth
};
App.js
import './App.css';
import Home from './components/Pages/Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Signup from './components/Pages/Signup/Signup';
import Login from './components/Pages/Login/Login';
import UserDashboard from './components/Pages/UserDashboard/UserDashboard';
import ForgotPassword from './components/Pages/Forgot-Password';
function App() {
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/signup" component={Signup}/>
<Route path="/login" component={Login}/>
<Route path="/dashboard" component={UserDashboard}/>
<Route path="/request-password-reset" component={ForgotPassword}/>
</Switch>
</div>
</Router>
);
}
export default App;
If you are trying to create a private route component without persisting the authentication state somewhere in your app and exposed out via a React context then you will need to check the auth status asynchronously on each route change. This means you'll also need a "loading" or "pending" state while the auth status check occurring.
Here's an example implementation of just a custom private route sans any persisted state.
import { useEffect, useState } from 'react';
import { Route, Redirect } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';
const PrivateRoute = props => {
const [pending, setPending] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribe = onAuthStateChanged(
auth,
user => {
setCurrentUser(user);
setPending(false);
},
error => {
// any error logging, etc...
setPending(false);
}
);
return unsubscribe; // <-- clean up subscription
}, []);
if (pending) return null; // don't do anything yet
return currentUser
? <Route {...props} /> // <-- render route and component
: <Redirect to="/login" />; // <-- redirect to log in
};
react-router-dom#6
Custom route components are out in v6, use a layout route. The PrivateRoute component will replace Route with Outlet for nested routes to render their matched element prop into, and Navigate replaces Redirect.
import { useEffect, useState } from 'react';
import { Outlet, Navigate } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';
const PrivateRoute = props => {
const [pending, setPending] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribe = onAuthStateChanged(
auth,
user => {
setCurrentUser(user);
setPending(false);
},
error => {
// any error logging, etc...
setPending(false);
}
);
return unsubscribe; // <-- clean up subscription
}, []);
if (pending) return null; // don't do anything yet
return currentUser
? <Outlet /> // <-- render outlet for routes
: <Navigate to="/login" replace />; // <-- redirect to log in
};
Wrap the routes you want to protect.
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/request-password-reset" element={<ForgotPassword />} />
<Route element={<PrivateRoute />}>
<Route path="/dashboard" element={<UserDashboard />} />
</Route>
</Routes>
</Router>
);
}
I've created a request method so I can easily fetch it, but when I first run it, I recevied this message.
TypeError: Cannot read properties of null (reading 'user')
I know where I get this, it's because my program automatically calls it when I start it automatically.
So what I want is to stop running these codes as long as I'm not logged in.
login.jsx
import { useState } from "react"
import { useDispatch } from "react-redux"
import { useHistory } from "react-router"
import { login } from "../../redux/apiCalls"
const Login = () => {
let history = useHistory()
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const dispatch = useDispatch()
const handleClick = (e) =>{
e.preventDefault()
login(dispatch,{username,password})
history.push('/home')
}
return (
<div style={{display: 'flex', alignItems:"center", justifyContent:"center", height: '100vh', flexDirection: "column"}}>
<form action="">
<input style={{padding: 10, marginBottom:20}} value={username} type="text" placeholder="username" onChange={e => setUsername(e.target.value)} />
<input style={{padding: 10, marginBottom:20}} value={password} type="password" placeholder="password" onChange={e => setPassword(e.target.value)} />
<button style={{padding: 10, width: 100}} onClick={handleClick}>Login</button>
</form>
</div>
)
}
export default Login
requestMethod.jsx
import axios from 'axios'
const BASE_URL = 'http://localhost:5000/api'
const TOKEN =
JSON.parse(JSON.parse(localStorage.getItem('persist:root')).user).currentUser
.accessToken || null
export const publicRequest = axios.create({
baseURL: BASE_URL,
})
export const userRequest = axios.create({
baseURL: BASE_URL,
headers: { token: `Bearer ${TOKEN}` },
})
app.js
import { useSelector } from 'react-redux'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import PrivateRoute from '../src/components/PrivateRoute'
import './App.css'
// import Sidebar from './components/sidemenu/Sidebar'
import Home from './pages/home/Home'
import Login from './pages/login/Login'
function App() {
const admin = useSelector((state) => state.user?.currentUser?.isAdmin)
return (
<Router>
<Switch>
<Route exact path="/login" component={Login} />
<PrivateRoute path="/home" auth={admin} component={Home}></PrivateRoute>
</Switch>
</Router>
)
}
export default App
I have to pass my state from Authentication.js to App.js, to make certain functionalities available to registered users.
This is my file Authentication.js
import { useState } from "react"
import MyButton from "../componens/myButton"
import styles from "../style/authentication.module.css"
import Spinner from "../componens/Spinner"
import axios from "axios"
const Authentication = () =>{
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [loading, setLoading] = useState(false)
const [registration, setRegistration] = useState(true)
const [state, setState] = useState(null)
let MyRegistration = null;
const handleSubmit = async (e) =>{
e.preventDefault()
setLoading(true)
try {
const userKey = `AIzaSyCYe1XVuJVflRie0sf1y01RNrmQ77Dmp4Q`
let urlAPI = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${userKey}`
if (!registration) {
urlAPI = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${userKey}`
}
setEmail(email);
setPassword(password);
MyRegistration = await axios.post(urlAPI,{
email : email,
password : password,
returnSecureToken : true
})
setState(MyRegistration)
setLoading(false);
return MyRegistration
} catch (error) {
setLoading(false)
console.log(error)
}
}
return <div className={styles.formContainer}>
<h1>Authentication</h1>
<form onSubmit={handleSubmit} action="">
<p>Email</p>
<input placeholder="Your email" type="email" value={email} onChange={handleEmail} />
<p>Password</p>
<input placeholder="Your Password" type="password" value={password} onChange={handlePassword} />
{loading ? <Spinner/> :
<MyButton title={registration ? 'sign up' : 'Sign in'} handleClickButton={handleSubmit} />}
<MyButton title={registration ? 'Go to Sign in' : 'Go to sign up'} handleClickButton={changeMode}/>
</form>
</div>
}
export default Authentication;
This is my file App.js
import Home from './pages/Home';
import Book from './pages/Book'
import {Route, Switch} from 'react-router-dom';
import SavedBooks from './pages/SavedBooks';
import Header from './componens/Header';
import BookChapter from './pages/BookChapter';
import Authentication from './pages/Authentication';
import {useState} from "react";
function App(props) {
const [userData, setUserData] = useState("");
return (
<div>
<Header/>
<Switch>
<Route exact path="/" render={(props) => <Home userData={userData}/>}/>
<Route exact path="/book/:id" component={Book}/>
<Route exact path="/savedbooks" component={SavedBooks}/>
<Route exact path="/book/:id/chapter/:numero" component={BookChapter}/>
<Route exact path="/authentication" render={(props) => <Authentication setUserData={setUserData(this.state)}/> }/>
</Switch>
</div>
)
}
export default App;
But the response is: TypeError: Cannot read properties of undefined (reading 'state')
You cannot pass the state declared in Authentication.js to its parent App.js . So, you need to make a useState in App.js and pass the state and its corresponding setterFunction onto the child i.e Authentication.js. This is known as lifting up the state.
Move const [state, setState] = useState(null) to App.js
and use the state variable in App.js and pass it onto child like this
<Route exact path="/authentication" render={(props) => <Authentication state={state} setState={setState}/> }/>
I have two things I'd like to know: First my application's navigation bar does not properly update to the guest navbar when logging out, but does update to the user navbar when a nonadmin account logs into it. If I do a page refresh (F5) after logging out it does update back to the guest navbar but that defeats the purpose of this being an SPA (Single page application).
The second thing I'd like to know is that currently my application uses React Context and Session Storage for authentication but I only use the Session Storage to actually get the information for the navigation bar. The reasoning is because Session Storage actually keeps the information after a page refresh while React Context forgets it. Is it better practice to use Session Storage for user authentication in a React application or should I just use React Context?
auth.js:
import { createContext, useContext } from "react";
export const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
App.js:
import { useState, useEffect } from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { AuthContext } from './context/auth';
import "bootstrap/dist/css/bootstrap.min.css";
/* Begin Import Components */
import NavigationBar from "./components/navbar";
import Public from "./components/public-components/public";
/* End Import Components */
function App(props) {
const [currentUser, setCurrentUser] = useState();
// Temporary for testing
const [currentTime, setCurrentTime] = useState(0);
useEffect(() => {
fetch('/time').then(res => res.json()).then(data => {
setCurrentTime(data.time);
});
}, []);
const setAuth = (data) => {
if(data) {
sessionStorage.setItem('username', data.username);
sessionStorage.setItem('isAdmin', data.isAdmin);
setCurrentUser({username: data.username, isAdmin: data.isAdmin});
console.log('Data in setAuth: ', data);
} else {
sessionStorage.setItem('username', null);
sessionStorage.setItem('isAdmin', null);
}
}
return (
<AuthContext.Provider value={{ currentUser, setCurrentUser: setAuth }}>
<Router>
<NavigationBar />
<div className="container text-center">
<Public />
<p>The current time is {currentTime}.</p>
</div>
</Router>
</AuthContext.Provider>
);
}
export default App;
navbar.js:
import { Navbar, Nav, Container } from 'react-bootstrap';
import { Link } from "react-router-dom";
function Navigation(props) {
const username = sessionStorage.getItem('username');
const isAdmin = sessionStorage.getItem('isAdmin');
const styledNavLink = {
textDecoration: "none",
color: "#F5F5F5",
marginLeft: "10px",
marginTop: "5px"
}
const styledHeader = {
textDecoration: "none",
color: "#FFF",
fontSize: "1.5em"
}
function adminNav() {
return (
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Link to="#" style={styledNavLink}>Admin</Link>
</Nav>
<Nav>
<Link to={`/${username}`}>Welcome, {username}</Link>
<Link to="/logout" style={styledNavLink}>Log Out</Link>
</Nav>
</Navbar.Collapse>
)
}
function loggedInNav() {
return (
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Link to="#" style={styledNavLink}>User</Link>
</Nav>
<Nav>
<Link to={`/${username}`} style={styledNavLink}>Welcome, {username}</Link>
<Link to="/logout" style={styledNavLink}>Log Out</Link>
</Nav>
</Navbar.Collapse>
)
}
function guestNav() {
return(
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Link to="#" style={styledNavLink}>Guest</Link>
</Nav>
<Nav>
<Link to="/register" style={styledNavLink}>Register</Link>
<Link to="/login" style={styledNavLink}>Login</Link>
</Nav>
</Navbar.Collapse>
)
}
return (
<Navbar bg="primary" variant="dark" expand="md">
<Container>
<Link to="/" style={styledHeader}>Flaskagram</Link>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
{username !== 'null'
?
isAdmin === 'true'
?
adminNav()
: loggedInNav()
: guestNav()
}
</Container>
</Navbar>
)
}
export default Navigation;
login.js:
import { useState } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { useAuth } from '../../context/auth';
import { Col, Form, Row, Button } from 'react-bootstrap';
function Login(props) {
const [loggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { setCurrentUser } = useAuth();
let referer;
if(props.location.state !== undefined) {
referer = props.location.state.referer;
} else {
referer = "/";
}
function postLogin() {
const formData = new FormData();
formData.append('email', email);
formData.append('password', password);
fetch('/login', {
method: 'post',
body: formData
}).then(res => res.json())
.then(data => {
if(data.wrongPassword) {
setIsError(true);
} else {
setCurrentUser({username: data.user.username, isAdmin: data.user.isAdmin});
setLoggedIn(true);
}
}).catch(err => {
console.log(err);
setIsError(true);
});;
}
if(loggedIn) {
return <Redirect to={referer} />;
}
return (
<main>
<header>
<h2>Login</h2>
</header>
<section>
<Form>
<Row className="justify-content-sm-center">
<Form.Group as={Col} sm={{ span: 6 }}>
<Form.Label htmlFor="email">Email</Form.Label>
<Form.Control
controlid="email"
type="email"
value={email}
onChange={e => {
setEmail(e.target.value);
}}
placeholder="Enter email"
autoFocus
/>
</Form.Group>
</Row>
<Row className="justify-content-sm-center">
<Form.Group as={Col} sm={{ span: 6 }}>
<Form.Label htmlFor="password">Password</Form.Label>
<Form.Control
controlid="password"
type="password"
value={password}
onChange={e => {
setPassword(e.target.value);
}}
placeholder="Enter password"
/>
</Form.Group>
</Row>
<Button onClick={postLogin} variant="success">Login</Button>
</Form>
</section>
<Link to="/register">Don't have an account?</Link>
{ isError &&<p>There was a problem logging in!</p> }
</main>
)
}
export default Login;
logout.js:
import { useState, useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import { useAuth } from '../../context/auth';
function Logout(props) {
const [isLoggedIn, setisLoggedIn] = useState(true);
const { setCurrentUser } = useAuth();
useEffect(() => {
setCurrentUser();
setisLoggedIn(false);
}, [ setCurrentUser ]);
if(!isLoggedIn) {
return <Redirect to={"/"} />;
}
return (
<div></div>
);
}
export default Logout;
Found the issue. The issue was in setAuth() in App.js. I wasn't updating the context when logging out (when data wasn't defined in the if/else statement) so the navbar remained in the logged in state because of it.
updated setAuth in App.js:
const setAuth = (data) => {
if(data) {
sessionStorage.setItem('username', data.username);
sessionStorage.setItem('isAdmin', data.isAdmin);
setCurrentUser({username: data.username, isAdmin: data.isAdmin});
} else {
sessionStorage.removeItem('username');
sessionStorage.removeItem('isAdmin');
setCurrentUser();
}
}
When logout happens you need to remove the items, you set in SessionStorage as follows in Logout component.
import { useState, useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import { useAuth } from '../../context/auth';
function Logout(props) {
const [isLoggedIn, setisLoggedIn] = useState(true);
const { setCurrentUser } = useAuth();
useEffect(() => {
setCurrentUser();
/* remove session storage items */
sessionStorage.removeItem('username');
sessionStorage.removeItem('isAdmin');
setisLoggedIn(false);
}, [ setCurrentUser ]);
if(!isLoggedIn) {
return <Redirect to={"/"} />;
}
return (
<div></div>
);
}
export default Logout;
In your navBar.js file, to check whether userName is empty then use !!userName to check whether it's not null or undefined.
Therefore, update return in Navigation component of navBar.js file as follows.
return (
<Navbar bg="primary" variant="dark" expand="md">
<Container>
<Link to="/" style={styledHeader}>Flaskagram</Link>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
{!!username
?
isAdmin === 'true'
?
adminNav()
: loggedInNav()
: guestNav()
}
</Container>
</Navbar>
)
I'm currently in the process of learning React JS.Tutorial D.O
I already have a PHP backend before and I want to create a login form where the result of the backend is JWT.
I use a custom hook in React JS for that.
Here is the code that I created.
App.js
import "./App.css";
import React from "react";
import { Routes } from "../config";
import { useToken } from "../hooks";
import Login from "./Login";
const App = () => {
const { token, setToken } = useToken();
if (!token) {
return <Login setToken={setToken} />;
}
return <Routes />;
};
export default App;
useToken()
import { useState } from "react";
export default function useToken() {
const getToken = () => {
const tokenString = localStorage.getItem("token");
const userToken = JSON.parse(tokenString);
return userToken?.token;
};
const [token, setToken] = useState(getToken());
const saveToken = (userToken) => {
localStorage.setItem("token", JSON.stringify(userToken));
setToken(userToken.token);
};
return {
setToken: saveToken,
token,
};
}
Login/index.js
import React, { useState } from "react";
import PropTypes from "prop-types";
import { Button, Card, Col, Container, Form, Row } from "react-bootstrap";
import "./login.css";
async function loginUser(credentials) {
const url = "v1/auth";
const user = new FormData();
user.append("username", credentials.username);
user.append("password", credentials.password);
return fetch(url, {
method: "POST",
body: user,
}).then((data) => data.json());
}
export default function Login({ setToken }) {
const [username, setUserName] = useState();
const [password, setPassword] = useState();
// Handle submit
const handleSubmit = async (e) => {
e.preventDefault();
const token = await loginUser({ username, password });
setToken(token);
};
return (
<div id="login-page">
<Container>
<Row className="d-flex justify-content-md-center align-items-center vh-100">
<Col sm={12} md={6}>
<Card>
<Form onSubmit={handleSubmit}>
<Card.Header>Sign In</Card.Header>
<Card.Body>
<Form.Group controlId="loginform-username">
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
placeholder="Username"
name="username"
onChange={(e) => setUserName(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="loginform-password">
<Form.Label>Password</Form.Label>
<Form.Control
name="password"
type="password"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Group>
</Card.Body>
<Card.Footer>
<Button
variant="primary"
type="submit"
className="float-right"
>
Login
</Button>
<div className="clearfix"></div>
</Card.Footer>
</Form>
</Card>
</Col>
</Row>
</Container>
</div>
);
}
Login.propTypes = {
setToken: PropTypes.func.isRequired,
};
When I login successfully, I save the token to local storage. But I got the following warning.
index.js:1 Warning: Failed prop type: The prop `setToken` is marked as required in `Login`, but its value is `undefined`.
How to solve this?. Any help it so appreciated
Updated
Routes
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { Login, Home } from "../../pages";
const Routes = () => {
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
};
export default Routes;
In Routes you call Login but never pass it setToken, which is required. One option is to pass the setToken function down through Routes:
App.js
import "./App.css";
import React from "react";
import { Routes } from "../config";
import { useToken } from "../hooks";
import Login from "./Login";
const App = () => {
const { token, setToken } = useToken();
if (!token) {
return <Login setToken={setToken} />;
}
return <Routes setToken={setToken} />;
};
export default App;
Routes.js
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { Login, Home } from "../../pages";
const Routes = ({ setToken }) => {
return (
<Router>
<Switch>
<Route path="/login">
<Login setToken={setToken} />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
};
export default Routes;
<Route path="/login">
<Login /> /* <--- this line is calling Login component but is passing setToken as undefined */
</Route>