I'm making a web authentication app in react based on a YouTube video from Web Dev Simplified (approx 20 min in)and have been running into an odd issue.
I saw someone post a similar question at the same point in development but I don't believe I have the same issue. I'm not getting any errors, the only issue I am having is on the error messages showing. When I submit an email and pair of mismatched passwords it should give me an error message, but I cannot get it to display any errors. This is the code:
./index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/App';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
./components/App.js
import React from "react";
import Signup from './Signup'
import { Container } from 'react-bootstrap'
import { AuthProvider } from '../contexts/AuthContext'
function App() {
return (
<AuthProvider>
<Container className="d-flex align-items-center justify-content-center" style={{ minHeight: "100vh" }}>
<div className="w-100" style={{ maxWidth: "400px" }}>
<Signup />
</div>
</Container>
</AuthProvider>
)
}
export default App;
./contexts/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()
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
return unsubscribe
}, [])
const value = { currentUser, signup }
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
./components/Signup.js
import React, { useRef, useState } from 'react';
import { Form, Button, Card, Alert } from 'react-bootstrap';
import { useAuth } from '../contexts/AuthContext';
export default function Signup() {
const emailRef = useRef();
const passwordRef = useRef();
const passwordConfirmRef = useRef();
const { signup } = useAuth();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match");
}
try {
setError("");
setLoading(true);
await signup(emailRef.current.value, passwordRef.current.value);
} catch {
setError("Failed to create an account");
}
setLoading(false);
}
return (
<>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Sign Up</h2>
{error && <Alert variant="danger">{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control type="email" ref={emailRef} required />
</Form.Group>
<Form.Group id="password">
<Form.Label>Password</Form.Label>
<Form.Control type="password" ref={passwordRef} required />
</Form.Group>
<Form.Group id="password-confirm">
<Form.Label>Password Confirmation</Form.Label>
<Form.Control type="password" ref={passwordConfirmRef} required />
</Form.Group>
</Form>
<Button disabled={loading} className="w-100" type="submit">
Sign Up
</Button>
</Card.Body>
</Card>
<div className="w-100 text-center mt-2">
Already have an account? Log In
</div>
</>
);
}
I've tried changing the [error, setError] assignment to const [error, setError] = useState(""); and it displayed the initially assigned error properly, but it still refuses to assign any subsequent messages. I've also tried to make sure all the names are correct, and uniform. I have been troubleshooting this for a couple days now to little success. JavaScript is not my main language, and I'm still learning it so I'm sure I'm just missing something simple. Thanks for the help!
I found the issue. I had placed the button outside of the card which caused it to not activate what it was supposed to. Moving the Button tag inside the Card tag fixed everything.
Related
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 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 am trying to use a container to simplify some styling while learning React and I am having some issues.
Currently this is my Component
import React from 'react'
import { Container, Row, Col } from 'react-bootstrap'
const FormContainer = ({ childern }) => {
return (
<Container>
<Row className="justify-content-md-center">
<Col xs={12} md={6}>
{childern}
</Col>
</Row>
</Container>
)
}
export default FormContainer
And this is where I am loading it into
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Form, Button, Row, Col } from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
import Message from '../components/Message'
import Loader from '../components/Loader'
import FormContainer from '../components/FormContainer'
import { login } from '../actions/userActions'
const LoginScreen = ({location, history}) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dispatch = useDispatch()
const userLogin = useSelector(state => state.userLogin)
const {loading, error, userInfo} = userLogin
const redirect = location.search ? location.search.split('=')[1] : '/' // Takes location of Url, splits after the equals sign, and everything right of it or takes us back to '/'
useEffect(()=> {
if(userInfo) {
history.push(redirect)
}
}, [history, userInfo, redirect])
const submitHandler = (e) => {
e.preventDefault()
dispatch(login(email, password))
}
return (
<FormContainer>
<h1>Sign In</h1>
{error && <Message variant='danger'>{error}</Message>}
{loading && <Loader/>}
<Form onSubmit={submitHandler}>
<Form.Group controlId='email'>
<Form.Label>Email Address</Form.Label>
<Form.Control type='email' placeholder="Enter email" value={email} onChange={(e) => setEmail(e.target.value)}>
</Form.Control>
</Form.Group>
<Form.Group controlId='password'>
<Form.Label>Password</Form.Label>
<Form.Control type='password' placeholder="Enter password" value={password} onChange={(e) => setPassword(e.target.value)}>
</Form.Control>
</Form.Group>
<Button type='submit' variant='primary'>
Sign In
</Button>
</Form>
<Row className='py-3'>
<Col>
New Customer <Link to={redirect ? `/register?redirect=${redirect}` : '/register'}>
Register
</Link>
</Col>
</Row>
</FormContainer>
)
}
export default LoginScreen
When I remove FormContainer and replace it with a div the LoginScreen info shows up but without the styling. Otherwise it is blank with no issues in the Console. My other components seem to to show some stuff, but not the text.
Any help would be amazing.
Thanks!
Actually i'm building a Facebook Mesenger Clone in React.js and I used material UI. When we run the code it asks for username. after providing that we can send message in the app. But as soon as we send the message we only get username printed on the screen .
The message is not being displayed on the webpage. This is App.js code:
import React, {useEffect, useState} from 'react';
import {Button, FormControl, Input, InputLabel} from "#material-ui/core";
import Message from "./Message";
import './App.css';
function App() {
const [input, setInput] = useState('');
const [messages, setMessages] = useState([]);
const [username, setUsername] = useState('');
useEffect(() => {
setUsername(prompt('Please enter your name'));
}, [])
const sendMessage = (event) => {
event.preventDefault();
setMessages([...messages, {username: username, text: input}]);
setInput('');
}
return <div className="App">
<h1>Hey Mate! 😃</h1>
<h2>Welcome {username}</h2>
<form>
<FormControl>
<InputLabel>Enter a message...</InputLabel>
<Input value={input} onChange={event => setInput(event.target.value)}/>
<Button disabled={!input} variant="contained" color="primary" type='submit' onClick={sendMessage}>Send
Message</Button>
</FormControl>
</form>
{
messages.map(message => <Message username={username} message={message}/>)
}
</div>
}
export default App;
and this is message component's code:
import React from "react";
import {Card, CardContent, Typography} from "#material-ui/core";
import './Mesage.css';
function Message(message, username) {
const isUser = username === message.username;
return (
<div className={`message ${isUser && 'message_user'}`}>
<Card className={isUser ? "message_userCard" : "message_guestCard"}>
<CardContent>
<Typography color="white" variant="h5" component="h2">
{message.username}: {message.text}
</Typography>
</CardContent>
</Card>
</div>
)
}
export default Message;
Kindly help me
You are getting props in the Message component wrong. Just change it to
function Message({ message, username }) {
...
}
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>
);
};