I am a beginner and I am learning react js. I made a crud app for practice. I am implementing firebase to this app for authentication. But the problem is that I am not getting the updated value.
Index.jsx
Here in line no 11, I am not getting the latest user value.
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import ShowBooks from '../pages/ShowBooks';
import AddBook from '../pages/AddBook';
import EditBook from '../pages/EditBook';
import Login from '../pages/Login';
import Navbar from '../components/Navbar';
const Index = () => {
const { user } = useSelector(state => state.case);
let routes;
if (user === null || user === undefined) {
routes = (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/login" replace={true} />} />
</Routes>
);
} else {
routes = (
<>
<Navbar />
<Routes>
<Route path="/" element={<ShowBooks />} />
<Route path="/show-books" element={<ShowBooks />} />
<Route path="/add-book" element={<AddBook />} />
<Route path="/edit-book" element={<EditBook />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" replace={true} />} />
</Routes>
</>
);
}
return <BrowserRouter>{routes}</BrowserRouter>;
};
export default Index;
Here is the code of BooksSlice.jsx:
import { createSlice } from '#reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
const initialBooks = {
books: [
{
id: uuidv4(),
title: 'Ordinary Differential Equation',
author: 'Prof. Md. Abu Yousuf',
},
{ id: uuidv4(), title: 'CSE Kit', author: 'Gyan Bitan' },
],
user: JSON.parse(localStorage.getItem('user')) || null,
};
export const BooksSlice = createSlice({
name: 'books',
initialState: initialBooks,
reducers: {
showBooks: state => state,
addBook: (state, action) => {
state.books.push(action.payload);
},
deleteBook: (state, action) => {
const id = action.payload;
state.books = state.books.filter(book => book.id !== id);
},
updateBook: (state, action) => {
const { id, title, author } = action.payload;
const isBookExist = state.books.filter(book => book.id === id);
if (isBookExist) {
isBookExist[0].title = title;
isBookExist[0].author = author;
}
},
},
});
export const { showBooks, addBook, deleteBook, updateBook } =
BooksSlice.actions;
export default BooksSlice.reducer;
Here is the code of store.js
import { configureStore } from '#reduxjs/toolkit';
import booksReducer from './BooksSlice';
const store = configureStore({
reducer: {
case: booksReducer,
},
});
export default store;
And here is Login.jsx code:
import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase/firebase';
import { useNavigate } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(false);
const navigate = useNavigate();
// Login Handler Function
const loginHandler = event => {
event.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then(userCredential => {
// Signed in
const user = userCredential.user;
localStorage.setItem('user', JSON.stringify(user));
navigate('/show-books', { replace: true });
})
.catch(error => {
// const errorCode = error.code;
// const errorMessage = error.message;
setError(true);
});
};
return (
<div className="container pt-5">
{error && (
<p className="text-danger text-center">Wrong email or password!</p>
)}
<form className="w-75 mx-auto" onSubmit={loginHandler}>
<div className="form-floating mb-3">
<input
type="email"
className="form-control"
placeholder="Email"
value={email}
onChange={event => setEmail(event.target.value)}
required
/>
<label>Email</label>
</div>
<div className="form-floating my-3">
<input
type="password"
className="form-control"
placeholder="Password"
value={password}
onChange={event => setPassword(event.target.value)}
required
/>
<label>Password</label>
</div>
<button type="submit" className="btn btn-dark btn-lg text-light">
Log In
</button>
</form>
</div>
);
};
export default Login;
How can I get the updated user value on Index.jsx?
You need to create a "login" action and dispatch the authenticated user object to the store when a user authenticates.
Example:
export const BooksSlice = createSlice({
name: 'books',
initialState: initialBooks,
reducers: {
...
login: (state, action) => {
state.user = action.payload;
localStorage.setItem('user', JSON.stringify(action.payload));
},
},
});
export const {
showBooks,
addBook,
deleteBook,
updateBook,
login,
} = BooksSlice.actions;
export default BooksSlice.reducer;
...
import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { useDispatch } from 'react-redux';
import { auth } from '../firebase/firebase';
import { useNavigate } from 'react-router-dom';
import { login } from '../path/to/BooksSlice';
const Login = () => {
...
const navigate = useNavigate();
const dispatch = useDispatch();
// Login Handler Function
const loginHandler = event => {
event.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then(userCredential => {
// Signed in
const user = userCredential.user;
dispatch(login(user)); // <-- dispatch user object to store
navigate('/show-books', { replace: true });
})
.catch(error => {
// const errorCode = error.code;
// const errorMessage = error.message;
setError(true);
});
};
return (
...
);
};
Related
It is my Auth Login form. I have AuthContext. Let's try log in.
I've clicked "log in" button. And we see AuthContext "user" : underfind
Then click reload page, and here we go, we have logged in
Why does it work like that?
Login.js
import React, {useState} from 'react'
import {useLogin} from '../hooks/useLogin'
import { useNavigate } from 'react-router-dom'
const Login = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const {login, error, isLoading} = useLogin()
let navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
await login(email, password)
}
return(
<div className='container'>
<h1>
login page
</h1>
<form className={'d-flex flex-column'} onSubmit={handleSubmit}>
<h3>Log in</h3>
<label>Email:</label>
<input
type={'email'}
onChange={(e) => setEmail(e.target.value)}
/>
<label>Password</label>
<input
type={'password'}
onChange={(e) => setPassword(e.target.value)}
/>
<button disabled={isLoading} type={'submit'}>
Log in
</button>
{error && <div className={'error'}>
{error}
</div>}
</form>
</div>
)
}
export default Login;
useLogin.js
import { useState } from "react";
import { useAuthContext } from "./useAuthContext";
export const useLogin = () => {
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(null)
const {dispatch} = useAuthContext()
const login = async (email, password) => {
setIsLoading(true)
setError(null)
const response = await fetch('/api/user/login', {
method:'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
})
const json = await response.json()
if(!response.ok){
setIsLoading(false)
setError(json.error)
}
if(response.ok){
localStorage.setItem('user', JSON.stringify(json))
dispatch({type:'LOGIN', paylaod:json})
setIsLoading(false)
}
}
return {login, isLoading, error}
}
AuthContext.js
import { createContext, useReducer, useEffect } from "react";
export const AuthContext = createContext()
export const authReducer = (state, action) => {
switch(action.type){
case 'LOGIN':
return {user: action.payload}
case 'LOGOUT':
return {user:null}
default:
return state
}
}
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(authReducer, {
user:null
})
useEffect(() => {
const user = JSON.parse(localStorage.getItem('user'))
if(user){
dispatch({type:'LOGIN', payload: user})
}
}, [])
console.log('AuthContext state', state)
return(
<AuthContext.Provider value={{...state, dispatch}}>
{children}
</AuthContext.Provider>
)
}
useAuthContext.js
import { AuthContext } from "../context/AuthContext";
import { useContext } from "react";
export const useAuthContext = () => {
const context = useContext(AuthContext)
if(!context){
throw Error('useAuthContext must be used inside an AuthContextProvider')
}
return context
}
here is Navbar.js
import React from "react";
import { useAuthContext } from "../hooks/useAuthContext";
import { useLogout } from "../hooks/useLogout";
import { Link } from "react-router-dom";
const Nav = () => {
const {logout} = useLogout()
const {user} = useAuthContext()
const handleClick= () => {
logout()
}
return(
<div>
{user && (
<div className="bg-dark text-light align-item-center d-flex justify-content-between m-auto container-fluid p-2">
<span className="m-0 p-0 d-flex align-item-center">{user.email}</span>
<button className="btn btn-outline-danger" onClick={handleClick}>logout</button>
</div>
)}
{!user && (
<div className="d-flex justify-content-between">
<Link to='/'>
<button>HOME</button>
</Link>
<Link to='/login'>
<button>LOGIN</button>
</Link>
<Link to='/signup'>
<button>SIGNUP</button>
</Link>
</div>
)}
</div>
)
}
export default Nav
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthContextProvider } from './context/AuthContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);
app.js
import {BrowserRouter, Routes, Route} from 'react-router-dom'
import Nav from './components/Nav';
import Home from './pages/home';
import Login from './pages/login';
import Signup from './pages/signup';
function App() {
return (
<div>
<BrowserRouter>
<Nav/>
<Routes>
<Route path={'/'} element={<Home/>}/>
<Route path={'/login'} element={<Login/>}/>
<Route path={'/signup'} element={<Signup/>}/>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
I don't think it is server error.
There's a spelling mistake in useLogin payload is written wrong
dispatch({type:'LOGIN', paylaod:json});
// Should be: Payload
dispatch({type:'LOGIN', payload:json});
The root cause seems to be a typo:
dispatch({type:'LOGIN', paylaod:json})
has a typo in paylaod, so the reducer's
case 'LOGIN':
return {user: action.payload}
basically just sets {user: undefined}.
You should add error checking to your reducer, or better yet switch to a typed language like TypeScript so typos like these are caught at type-check time.
Since the user is not in the component state, it will not re-render after it is set (which seems to happen after first rendering). Add a useEffect with dependency on the value that updates the state and it should be good. F.ex:
const Nav = () => {
const {contextUser} = useAuthContext()
const [user, setUser] = useState()
...
useEffect(() => {
if (contextUser) {
setUser(contextUser)
}
}, [contextUser])
...
}
I am trying to learn firebase by reading their documentation. I have added login using google account and email password. When I click on log in, I am dispatching setLogInValue and the state is updating but getting the value of isLoggedIn: undefined. But when I first visit the page the value of isLoggedIn is null. I expect the value of isLoggedIn: true after clicking on log in and redirect to homepage but as I am getting undefined the routing is not working.
slice.js
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
email: localStorage.getItem('email'),
isLoggedIn: localStorage.getItem('loggedInStatus'),
};
export const slice = createSlice({
name: 'slice',
initialState,
reducers: {
setLogInValue: (state, action) => {
state.email = localStorage.setItem('email', action.payload);
state.isLoggedIn = localStorage.setItem('loggedInStatus', true);
},
},
});
export const { setLogInValue } = slice.actions;
export default slice.reducer;
Login.jsx
import React, { useState } from 'react';
import {
getAuth,
signInWithPopup,
signInWithEmailAndPassword,
GoogleAuthProvider,
} from 'firebase/auth';
import { useDispatch } from 'react-redux';
import { setLogInValue } from '../redux/slice';
import { useNavigate } from 'react-router-dom';
import { auth } from '../firebase/firebase';
const Login = () => {
const [input, setInput] = useState({ email: '', password: '' });
const inputHandler = event => {
setInput({ ...input, [event.target.name]: event.target.value });
};
const dispatch = useDispatch();
const navigate = useNavigate();
// const auth = getAuth();
const googleSignIn = () => {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then(result => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info.
const user = result.user;
dispatch(setLogInValue(user.email));
// localStorage.setItem('loggedInStatus', true);
navigate('/', { replace: true });
})
.catch(error => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
// The email of the user's account used.
const email = error.customData.email;
// The AuthCredential type that was used.
const credential = GoogleAuthProvider.credentialFromError(error);
// ...
console.log(errorMessage);
});
};
const submitHandler = event => {
event.preventDefault();
// const auth = getAuth();
signInWithEmailAndPassword(auth, input.email, input.password)
.then(userCredential => {
// Signed in
const user = userCredential.user;
console.log(user);
dispatch(setLogInValue(input.email));
setInput({
email: '',
password: '',
});
navigate('/', { replace: true });
})
.catch(error => {
const errorCode = error.code;
const errorMessage = error.message;
console.log(errorMessage);
});
};
return (
<div className="container">
<form
className="w-75 mx-auto mt-5"
onSubmit={event => submitHandler(event)}>
<div className="form-floating mb-3">
<input
name="email"
type="email"
className="form-control"
placeholder="name#example.com"
value={input.email}
onChange={inputHandler}
/>
<label>Email address</label>
</div>
<div className="form-floating">
<input
name="password"
type="password"
className="form-control"
placeholder="Password"
value={input.password}
onChange={inputHandler}
/>
<label>Password</label>
</div>
<button
type="submit"
className="btn btn-primary btn-lg d-block mx-auto mt-3">
Log in
</button>
</form>
<button
className="btn btn-primary btn-lg d-block mx-auto mt-3"
onClick={googleSignIn}>
Log in With Google
</button>
</div>
);
};
export default Login;
Home.jsx
import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.min.js';
import 'bootstrap-icons/font/bootstrap-icons.css';
import { Routes, Route, Navigate } from 'react-router-dom';
import Nav from './components/Nav';
import Home from './pages/Home';
import Login from './pages/Login';
import { useSelector } from 'react-redux';
const App = () => {
const { isLoggedIn } = useSelector(state => state.info);
console.log('App:', isLoggedIn);
let routes;
if (isLoggedIn) {
routes = (
<>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</>
);
} else {
routes = (
<>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/login" replace />} />
</Routes>
</>
);
}
return <>{routes}</>;
};
export default App;
Nav.jsx
import React from 'react';
import { Link } from 'react-router-dom';
const Nav = () => {
return (
<nav className="navbar navbar-expand-lg bg-light ">
<div className="container">
<Link className="navbar-brand" to="/">
Navbar
</Link>
<Link className="nav-link" to="/">
Home
</Link>
</div>
</nav>
);
};
export default Nav;
Why I am getting undefined instead of "true/false/" of isLoggedIn value from the localStorage? How can I fix that?
The problem is in the code in 'slice.js':
setLogInValue: (state, action) => {
state.email = localStorage.setItem('email', action.payload);
state.isLoggedIn = localStorage.setItem('loggedInStatus', true); // <<<< Problem here: function localStorage.setItem return 'undefined' value
},
so you should fix like this:
setLogInValue: (state, action) => {
localStorage.setItem('loggedInStatus', true)
localStorage.setItem('email', action.payload);
state.email = action.payload;
state.isLoggedIn = true;
},
I have a react js app with react-router-dom v6 to handle the routes. The routes functionality worked just fine before i added firebase firestore, but now for some reason when i'm in the seeker page and i reload it, the home page gets rendered. It's not a problem with user authentication because that's handled by the login component, but i couldn't find the problem in my configuration.
This is my app component.
import React, {useEffect, useState} from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { createBrowserHistory } from 'history';
import ReactDOM from "react-dom";
import "./styles/style.scss";
import Home from "./pages/home";
import Seeker from "./pages/seeker";
import NotFound from "./pages/notFound";
import Login from "./pages/login";
const App = () => {
const [token, setToken] = useState(false);
useEffect(() => {
if(localStorage.getItem("token") === null){
setToken(false);
} else {
setToken(true);
}
}, [])
return (
<BrowserRouter history={createBrowserHistory}>
<Routes>
<Route exact path="/" element={<Login isAuth={token}/>}/>
<Route exact path="/home" element={token ? <Home /> : <Navigate to="/"/>}/>
<Route exact path="/seeker" element={token ? <Seeker /> : <Navigate to="/"/>}/>
<Route path="*" element={<NotFound />}/>
</Routes>
</BrowserRouter>
)
}
ReactDOM.render(
<App />,
document.getElementById("root"))
The login component sets the token on localstorage correctly and the routes use it to render conditionally the components. But when i'm in the "/seeker" url the refresh takes me to "/home".
import React, { useState, useEffect } from "react";
import { Formik, Form, Field, ErrorMessage } from 'formik';
import axios from "axios";
require("babel-core/register");
require("babel-polyfill");
import GridRender from "../components/gridRender";
import { NavBar } from "../components/navBar";
async function getHeros(name) {
return await axios.get(
`https://www.superheroapi.com/api.php/${name}`
);
}
const Seeker = () => {
const [searchedHero, setSearchedHero] = useState("")
const [matchedHeros, setMatchedHeros] = useState([]);
useEffect(() => {
let isMounted = true;
if (isMounted) {
getHeros(searchedHero).then((heros) => {
const hero = heros.data.response != "error" ? heros.data.results : []
localStorage.setItem("addHeroAction", "true");
setMatchedHeros(hero);
}, console.error);
}
return () => { isMounted = false };
}, [searchedHero]);
return (
<div>
<NavBar />
<div className="seeker">
<h1 className="title">Find your next teammate!</h1>
<Formik /> //// here is a large form that i erased so it doesn't get long
{matchedHeros.length != 0 && <GridRender matchedHeros = {matchedHeros}/>}
</div>
</div>
)
}
export default Seeker;
And here is my firebase config (i use version 9) in case it's important because the bug appeared after firebase was implemented.
import { initializeApp } from "firebase/app";
import "regenerator-runtime/runtime";
import { addDoc, deleteDoc, doc } from 'firebase/firestore';
import { getFirestore, collection } from 'firebase/firestore'
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxxxxxxxxxxxxxx",
databaseURL: "xxxxxxxxxxxxxxxxxxxxx",
projectId: "superteam-maker",
storageBucket: "xxxxxxxxxxxxxxx",
messagingSenderId: "xxxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
};
initializeApp(firebaseConfig);
const db = getFirestore();
const colRef = collection(db, "team")
const addHero = (hero) => {
addDoc(colRef, {
name : hero.name,
powerstats :{
combat: parseInt(hero.powerstats.combat),
durability: parseInt(hero.powerstats.durability),
intelligence: parseInt(hero.powerstats.intelligence),
power: parseInt(hero.powerstats.power),
speed: parseInt(hero.powerstats.speed),
strength : parseInt(hero.powerstats.strength)
},
id: parseInt(hero.id),
image: {url : hero.image.url},
} )
}
const deleteHero = (hero) => {
const docRef = doc(db, "team", hero.id);
deleteDoc(docRef);
}
export {addHero, deleteHero, colRef}
////////UPDATE/////////
The login component autehnticates the user with a test API that returns a random token, so the user can only enter if a certain email and password get subbmited. If the auth is successfull the user gets rendered to "/home".
Here's the code fro the login component.
import React, {useEffect, useState} from "react";
import { Formik } from 'formik';
import { Navigate } from 'react-router-dom';
import axios from "axios";
const Login = (props) => {
const [token, setToken] = useState(false);
useEffect(() => {
if(localStorage.getItem("token") === null){
setToken(false);
} else {
setToken(true);
}
}, [])
const verification = () => {
if( token && props.isAuth){
return <Navigate to="/home"/>
}
}
let logInError = {};
return(
<div className="login__background">
<div className="login container">
{verification()}
<h1 className="display-1 title login__title">Make a team as <span>powerfull</span> as you!</h1>
<h5 className="login-title__sub">Please log in to create your superteam</h5>
<Formik
initialValues={{ email: '', password: '' }}
validate={values => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = 'Invalid email address';
}
if (!values.password){
errors.password = "Required";
}
return errors;
}}
onSubmit={(values) => {
axios.post("http://challenge-react.alkemy.org/", values)
.then(response => {
let token = response.data.token
localStorage.setItem("token", token);
localStorage.setItem("addHeroAction", false);
setToken(true);
return <Navigate to="/home"/>
})
.catch(error => {
if (error.response) {
logInError = error.response.data.error;
window.alert(logInError);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
})
}}>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit
}) => (
<form onSubmit={handleSubmit}>
<label className="form-label label" name="email">Email</label>
<br />
<input
className="form-control form-control-lg input"
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<br />
<label className="form-label label " name="password">Password</label>
<br />
<input
className="form-control form-control-lg input"
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{errors.password && touched.password && errors.password}
<br />
<button type="submit" className="btn btn-danger">
Submit
</button>
</form>
)}
</Formik>
</div>
</div>
)
}
export default Login;
It also gets an IsAuth propr from App.js in case it's the first time the user enters the page and there is no token saved.
The issue I see is the token state being initially false. The useEffect runs at the end of the render cycle, so for the initial render token is false
const [token, setToken] = useState(false);
useEffect(() => {
if (localStorage.getItem("token") === null) {
setToken(false);
} else {
setToken(true);
}
}, []);
and any Route being rendered using the token state will use this initial false value and act accordingly.
<Route path="/seeker" element={token ? <Seeker /> : <Navigate to="/" />} />
If on the "/seeker" path and the token value is false, the Navigate component is rendered.
Assuming all your authentication flow is correct and working and sets the token value into localStorage then you should initialize the token state from localStorage.
const initializeState = () => !!JSON.parse(localStorage.getItem("token"));
const [token, setToken] = useState(initializeState);
I am having problem on my reactjs. I can't open login.
setEmail is not function and for setPassword is same. I tried to edit and checked my codes but still and error. what should I do?
I am already run it. but when I create my username there is an error.
here the error
here the codes for Login.js
import React from 'react';
const login = (props) => {
const { email, setEmail, password, setPassword, handleLogin, handleSignup, hasAccount, setHasAccount, emailError, passwordError} = props;
return (
<section className="login">
<div className="loginContainer">
<label htmlFor="">Username</label>
<input type="text"
autoFocus
required
value={email}
onChange={e => setEmail(e.target.value)}
/>
<p className="errorMsg">{ emailError }</p>
<label>Password</label>
<input
type="password"
requited
value={password}
onChange={e => setPassword(e.target.value)}
/>
here for App.js
App.js
import React, {useState, useEffect} from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
// import Login from '../Login';
import routes from "./routes";
import withTracker from "./withTracker";
import "bootstrap/dist/css/bootstrap.min.css";
import "./shards-dashboard/styles/shards-dashboards.1.1.0.min.css";
import './App.css';
import fireDb from './firebase/firebase';
import './App.css';
//Components
import Dashboard from "./views/Dashboard";
import Login from "./views/Login";
function App() {
//States
const [user, setUser] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const [hasAccount, setHasAccount] = useState(false);
const clearInputs = () => {
setEmail('')
setPassword('')
}
const clearErrors = () => {
setEmailError('')
setPasswordError('')
}
//Login function
const handleLogin = () => {
clearErrors();
fireDb.auth()
.signInWithEmailAndPassword(email, password)
.catch(error => {
switch (error.code) {
case 'auth/invalid-email':
case 'auth/user-disabled':
case 'auth/user-not-found':
setEmailError(error.message);
break;
case 'auth/wrong-password':
setPasswordError(error.message);
break;
}
})
}
//Signup function
const handleSignup = () => {
clearErrors();
fireDb.auth()
.createUserWithEmailAndPassword(email, password)
.catch(error => {
switch (error.code) {
case 'auth/email-already-in-use':
case 'auth/invalid-email':
setEmailError(error.message);
break;
case 'auth/weak-password':
setPasswordError(error.message);
break;
}
})
}
//Function for logout
const handleLogout = () => {
fireDb.auth().signOut();
}
//Authentication listener
const authListener = () => {
fireDb.auth().onAuthStateChanged(user => {
if ( user ){
clearInputs();
setUser(user);
} else { setUser('') }
})
}
//React listener.
useEffect(() => {
authListener();
},[]);
return (
<div className="App">
{ user ? (
<Dashboard handleLogout={handleLogout} />
) : (
<Login
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
handleLogin={handleLogin}
handleSignup={handleSignup}
hasAccount={hasAccount}
setHasAccount={setHasAccount}
emailError={emailError}
passwordError={passwordError}
/>
)}
</div>
);
}
export default () => (
<Router basename={process.env.REACT_APP_BASENAME || ""}>
<div>
{/*
<Dashboard/> */}
{routes.map((route, index) => {
return (
<Route
key={index}
path={route.path}
exact={route.exact}
component={withTracker(props => {
return (
<route.layout {...props}>
<route.component {...props} />
</route.layout>
);
})}
/>
);
})}
</div>
</Router>
);
// export default App;
[the errors can not setEmail][2]
This issue is because your login component is loading directly without any props. You should load the login component through the App component to get all the props.
For that make the below changes in routes.js,
{
path: "/login",
layout: DefaultLayout,
component: App // export App and import here
},
Working Code - https://codesandbox.io/s/cranky-fog-efen8?file=/src/App.js
How can I pass the data from one React hooks form (component) to another component. For example if I need player name and photo pass from Profile.js and make it available in Navigation.js, how can i do that ?
Player.js
import React, { useContext , useEffect, useState } from "react";
import { useForm } from 'react-hook-form';
import { useHistory } from "react-router-dom";
import Axios from "axios";
import UserProfileContext from '../context';
const Profile = () => {
const { setData } = useContext(UserProfileContext);
const [email, setEmail] = useState('');
const [picture, setPicture] = useState('');
const [playerProfile, setPlayerProfile] = useState([]);
const loginUserEmail = localStorage.getItem('loginEmail');
const [updateProfile, setUpdateProfile] = useState({ _id: '', photo: '', name: '', email:'', phonenumber: '', position: '', password: '' })
const [isSent, setIsSent] = useState(false);
const [helperText, setHelperText] = useState('');
const [disabled, setDisabled] = useState(true);
const { handleSubmit, register, errors } = useForm();
const history = useHistory();
const onChangePicture = e => {
console.log('picture: ', picture);
if (e.target.files.length) {
setPicture(URL.createObjectURL(e.target.files[0]));
} else {
return false;
}
};
// If no profile image is being uploaded, to avoid the broken display of image, display a default image.
const addDefaultSrc = e => {
e.target.src = '/images/default-icon.png';
}
// Pass the id to the handler so you will know which item id changing.
const handleChange = (e, id) => {
e.persist();
let itemIndex;
const targetPlayer = playerProfile.find((player, index) => {
console.log({ player, id, index });
itemIndex = index; // Track the index so you can use it to update later.
return player.id === id;
});
console.log({ targetPlayer, id, e });
const editedTarget = {
...targetPlayer,
[e.target.name]: e.target.value
};
const tempPlayers = Array.from(playerProfile);
tempPlayers[itemIndex] = editedTarget;
/*
// Alternatively:: you can just map over the array if you dont want to track the index
const tempPlayers = playerProfile.map((profile, index) => {
return profile.id === id ? editedTarget : profile;
});
*/
setPlayerProfile(tempPlayers);
setUpdateProfile({ ...updateProfile, [e.target.name]: e.target.value }); // this is added just to see if its working
};
useEffect(() => {
const fetchData = async () => {
try {
const params = {
email: loginUserEmail,
};
const res = await Axios.get('http://localhost:8000/service/profile', {params});
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const onSubmit = () => {
setDisabled(disabled);
const fetchData = async () => {
try {
const params = {
email: loginUserEmail,
};
const data = {photo: updateProfile.photo, name: updateProfile.name, email: updateProfile.email, phonenumber: updateProfile.phonenumber, position: updateProfile.position, password: updateProfile.password}
const res = await Axios.put('http://localhost:8000/service/profile', data, {params});
console.log("Front End update message:" + res.data.success);
if (res.data.success) {
setIsSent(true);
history.push('/')
}
else {
console.log(res.data.message);
setHelperText(res.data.message);
}
} catch (e) {
setHelperText(e.response.data.message);
}
}
fetchData();
}
return (
<div className="register_wrapper">
<div className="register_player_column_layout_one">
<div className="register_player_Twocolumn_layout_two">
<form onSubmit={handleSubmit(onSubmit)} className="myForm">
{
playerProfile.map(({ id, photo, name, email, phonenumber, position, privilege, password }) => (
<div key={id}>
<div className="formInstructionsDiv formElement">
<h2 className="formTitle">Profile</h2>
<div className="register_profile_image">
<input id="profilePic" name="photo" type="file" onChange={onChangePicture} />
</div>
<div className="previewProfilePic" >
<img alt="" onError={addDefaultSrc} name="previewImage" className="playerProfilePic_home_tile" src={photo} onChange={e => handleChange(e, id)}></img>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
<input className="inputRequest formContentElement" name="name" type="text" value={name}
onChange={e => handleChange(e, id)}
maxLength={30}
ref={register({
required: "Full name is required",
pattern: {
value: /^[a-zA-Z\s]{3,30}$/,
message: "Full name should have minimum of 3 letters"
}
})}
/>
<span className="registerErrorTextFormat">{errors.name && errors.name.message}</span>
</label>
<label>
<input className="inputRequest formContentElement" name="email" type="text" value={email}
onChange={e => handleChange(e, id)}
disabled={disabled}
/>
</label>
<label>
<input className="inputRequest formContentElement" name="phonenumber" type="text" value={phonenumber}
onChange={e => handleChange(e, id)}
maxLength={11}
ref={register({
required: "Phone number is required",
pattern: {
value: /^[0-9\b]+$/,
message: "Invalid phone number"
}
})}
/>
<span className="registerErrorTextFormat">{errors.phonenumber && errors.phonenumber.message}</span>
</label>
<label>
<input className="inputRequest formContentElement" name="position" type="text" value={position}
onChange={e => handleChange(e, id)}
maxLength={30}
ref={register({
pattern: {
value: /^[a-zA-Z\s]{2,30}$/,
message: "Position should have minimum of 2 letters"
}
})}
/>
<span className="registerErrorTextFormat">{errors.position && errors.position.message}</span>
</label>
<label>
<div className="select" >
<select name="privilege" id="select" value={privilege} onChange={e => handleChange(e, id)}>
{/*<option selected disabled>Choose an option</option> */}
<option value="player">PLAYER</option>
{/*<option value="admin">ADMIN</option>*/}
</select>
</div>
</label>
<label>
<input className="inputRequest formContentElement" name="password" type="password" value={password}
onChange={e => handleChange(e, id)}
minLength={4}
maxLength={30}
ref={register({
required: "Password is required",
pattern: {
value: /^(?=.*?\d)(?=.*?[a-zA-Z])[a-zA-Z\d]+$/,
message: "Password begin with a letter and includes number !"
}
})}
/>
<span className="registerErrorTextFormat">{errors.password && errors.password.message}</span>
</label>
</div>
<label>
<span className="profileValidationText">{helperText}</span>
</label>
<div className="submitButtonDiv formElement">
<button type="submit" className="submitButton">Save</button>
</div>
</div>
))
}
</form>
</div>
</div>
</div>
);
}
Navigation.js
import React, { useContext } from 'react';
import { NavLink, useHistory } from 'react-router-dom';
import UserProfileContext from '../context';
const Navigation = () => {
const history = useHistory();
const { data } = useContext(UserProfileContext);
const divStyle = {
float:'left',
color: '#64cad8',
padding: '0px 0px 0px 10px',
font:'Lucida, sans-serif'
};
function logout() {
localStorage.removeItem('loginEmail')
localStorage.removeItem('Privilege')
history.push('/login')
window.location.reload(true);
}
return localStorage.getItem('loginEmail') &&
<div className="App">
<div className="wrapper">
<nav className="siteNavigation_nav_links">
<div className="clubLogo landing"style={divStyle}><b>Southside Soccer</b></div>
<NavLink className="mobile_register_link" to="/">Home</NavLink>
<NavLink className="mobile_register_link" to="/profile">Profile</NavLink>
<NavLink className="mobile_login_link" to="/login" onClick={logout}>Logout</NavLink>
<NavLink className="mobile_login_link" to='/aboutus'>About us</NavLink>
<div className="profileImage nav menu">
<span>{data.name}</span>|<img src=""></img>
</div>
</nav>
</div>
</div>
}
export default Navigation;
App.js
import React, { useState } from 'react';
import "./App.css";
import "./CSSModules/home.css";
import "./CSSModules/register.css";
import "./CSSModules/login.css";
import "./CSSModules/aboutus.css";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import Register from "./components/Register";
import Login from "./components/Login";
import Aboutus from "./components/Aboutus";
import Navigation from "./components/Navigation";
import Profile from "./components/Profile";
import { ProtectedRoute } from "./components/protected.route";
import UserProfileContext from './context';
var ReactDOM = require("react-dom");
const App = () => {
const [data, setData] = useState({
id: '',
name: '',
email: '',
photo: '',
});
return (
<UserProfileContext.Provider value={{ data, setData }}>
<BrowserRouter>
<>
<Navigation />
<Switch>
<ProtectedRoute exact path="/" component={Home} />
<ProtectedRoute path="/profile" component={Profile} />
<ProtectedRoute path="/aboutus" component={Aboutus} />
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
</Switch>
</>
</BrowserRouter>
</UserProfileContext.Provider>
);
};
ReactDOM.render(
React.createElement(App, null),
document.getElementById("root")
);
export default App;
context.js
import React from 'react';
export default React.createContext();
Like mentioned in the comments, one option is to left your application's state up (and that should be the preferred option for simple state).
In practice, that would look like:
App.js
import React, { useState } from 'react';
import Navigation from './Navigation';
import Profile from './Profile';
function App() {
const [name, setName] = useState('');
return (
<div className="App">
<Navigation name={name} />
<hr />
<Profile name={name} setName={setName} />
</div>
);
}
export default App;
Profile.js:
import React from 'react';
const Profile = ({ name, setName }) => {
return (
<>
<div>Profile: {name}</div>
<input
type="text"
name="name"
value={name}
onChange={e => setName(e.target.value)}
/>
</>
);
};
export default Profile;
Navigation.js:
import React from 'react';
const Navigation = ({ name }) => {
return <div>Navigation: {name}</div>;
};
export default Navigation;
Edit: After a closer look at your code, I think using context API makes more sense in this case.
Try the following:
context.js
import React from 'react';
export default React.createContext();
App.js
import React, { useState } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './styles.css';
import Profile from './components/Profile';
import Navigation from './components/Navigation';
import UserProfileContext from './context';
const App = () => {
const [data, setData] = useState({
id: 'player-1',
name: 'Player One',
age: 25,
photo: 'rose.JPG',
});
return (
<UserProfileContext.Provider value={{ data, setData }}>
<BrowserRouter>
<Navigation />
<Switch>
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
</UserProfileContext.Provider>
);
};
export default App;
components/Navigation.js
import React, { useContext } from 'react';
import { NavLink } from 'react-router-dom';
import UserProfileContext from '../context';
const Navigation = () => {
const { data } = useContext(UserProfileContext);
const divStyle = {
float: 'left',
color: '#64cad8',
padding: '0px 0px 0px 10px',
font: 'Lucida, sans-serif',
};
return (
<div className="App">
<div className="wrapper">
<nav className="siteNavigation_nav_links">
<div className="clubLogo landing" style={divStyle}>
<b>Soccer</b>
</div>
<NavLink className="mobile_register_link" to="/profile">
Profile
</NavLink>
<div className="profileImage nav menu">
<span>{data.name}</span> | <img alt="" src={data.photo} />
</div>
</nav>
</div>
</div>
);
};
export default Navigation;
components/Profile.js
import React, { useContext } from 'react';
import { useForm } from 'react-hook-form';
import UserProfileContext from '../context';
const Profile = () => {
const { setData } = useContext(UserProfileContext);
const { register, handleSubmit } = useForm();
return (
<div>
<form onSubmit={handleSubmit(setData)}>
<b>Profile</b>
<input name="id" ref={register} />
<input name="name" ref={register} />
<input name="age" ref={register} />
<button type="submit" className="submitButton">
Click
</button>
</form>
</div>
);
};
export default Profile;
You can utilize react-redux for global state handling or pass a callback function from Navigation.js to Profile.js.
Use React's built in context API. Wrap your App in context provider, then you can use useContext hook to access state, dispatch state updates between components.
App level setup - one time
// Define Context
const AppContext = React.createContext()
// Define initial state
const initialState = {}
// Define Reducer
const Reducer = (state, dispatch) => {
switch(action.type) {
case "ACTION_TYPE": return {...state, prop: action.payload}
default: return state
}
}
//Wrap main App in context, pass state from React's useReducer hook
const [state, dispatch] = useReducer(Reducer, initialState)
<AppContext.Provider data={{
state,
dispatch
}}>
<ReactAppMainWrapper />
</AppContext.Provider>
Component level
const {state, dispatch} = useContext(AppContext);
// to update state call dispatch from anywhere, all components consuming state will be updated
dispatch({
type: "ACTION_TYPE",
payload: "new_value"
})
Explanation
The state serves as redux like store, available in the entire app.
In any component, import AppContext and use React's builtin useContext hook to interact with store from the component.
Data object passed in Context provider above is available from this.