I am rying to update a 'user' from my useContext hook.
The use context checks with an axios call to see if the user is authenticated but would also handle the login and the logout.
here are my contexts and custom hooks I made to access those
authContextProvider.js
const AuthContext = React.createContext();
const LoginContext = React.createContext()
const LogoutContext = React.createContext();
const useAuth = () => {
return useContext(AuthContext);
}
const useLogin = () => {
return useContext(LoginContext)
}
const useLogout = () => {
return useContext(LogoutContext)
}
and this is my provider:
authContextProvider.js
const AuthProvider = ({children}) => {
const [auth, setAuth] = useState(false);
useEffect(() => {
axios.get("api/auth")
.then((result) => {
if(result.data.auth) {
setAuth(result.data.auth)
}
})
.catch(err => {
throw err;
})
}, [])
const login = (email, password) => {
axios.post('api/login', {
email: email,
password: password
})
.then((response) => {
console.log(response.data.auth)
setAuth(response.data.auth)
})
}
const logout = () => {
axios.get('api/logout')
.then(response => {
console.log("User has been logged out.")
setAuth(false)
})
}
return (
<AuthContext.Provider value={auth}>
<LoginContext.Provider value={login}>
<LogoutContext.Provider value={logout}>
{children}
</LogoutContext.Provider>
</LoginContext.Provider>
</AuthContext.Provider>
)
}
Problem:
In my login component, I would use the useLogin hook to trigger my API call to log in with an email and password. But I cannot seem to figure out how to pass those from my login component to my context as props.
Login.js
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// this is where my email and password would be send to my context
const handleLogin = useLogin()
return (
<>
Login
<div className={'loginForm'}>
<label htmlFor="email">Email</label>
<input type="email" id={'email'} name={'email'} onChange={(e) => {setEmail(e.target.value)}}/>
<br/>
<label htmlFor="password">Password</label>
<input type="password" id={'password'} name={'password'} onChange={(e) => {setPassword(e.target.value)}}/>
<br/>
<button onClick={() => handleLogin}>Go !</button>
</div>
<br/>
<br/>
< />
);
}
My quesion
How do I pass those props?
Or is this not intended and should this be done differently
Related
I'm new to React, and I'm trying to make a recpie app with react, right know I want to save the data in json file from the add form. so I can save the data but when I want to redirect the user to the home page using useEffict with navigate. I can't go to the create page when adding navigate to the useEffict.
Create file code:
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useFetch } from "../../hooks/useFetch";
// Styles
import "./Create.css";
export default function Create() {
const [title, setTitle] = useState("");
const [method, setMethod] = useState("");
const [cookingTime, setCookingTime] = useState("");
const [newIngredient, setNewIngredient] = useState("");
const [ingredients, setIngredients] = useState([]);
const { postData, data } = useFetch("http://localhost:3000/recipes", "POST");
const ingredientsInput = useRef(null);
const navigate = useNavigate();
// Methods
const handleSubmit = (e) => {
e.preventDefault();
postData({
title,
ingredients,
method,
cookingTime: cookingTime + " minutes",
});
};
const handleAdd = (e) => {
e.preventDefault();
const ing = newIngredient.trim();
if (ing && !ingredients.includes(ing)) {
setIngredients((preIng) => [...preIng, ing]);
}
setNewIngredient("");
ingredientsInput.current.focus();
};
useEffect(() => {
if (data) {
navigate("/");
console.log(data);
}
}, [data, navigate]);
return (
<div className="create">
<form onSubmit={handleSubmit}>
<label>
<span>Recipe Title:</span>
<input
type="text"
onChange={(e) => setTitle(e.target.value)}
value={title}
required
/>
</label>
<label>
<span>Recipe ingredients:</span>
<div className="ingredients">
<input
type="text"
onChange={(e) => setNewIngredient(e.target.value)}
value={newIngredient}
ref={ingredientsInput}
/>
<button onClick={handleAdd} className="btn">
Add
</button>
</div>
</label>
{ingredients.length > -1 && (
<p>
Current ingredients:{" "}
{ingredients.map((ing) => (
<span key={ing}>{ing}, </span>
))}
</p>
)}
<label>
<span>Recipe Method:</span>
<textarea
onChange={(e) => setMethod(e.target.value)}
value={method}
required
/>
</label>
<label>
<span>Recipe Time (minutes):</span>
<input
type="number"
onChange={(e) => setCookingTime(e.target.value)}
value={cookingTime}
required
/>
</label>
<button className="btn">Submit</button>
</form>
</div>
);
}
useFetch file code:
import { useState, useEffect } from "react";
export const useFetch = (url, method = "GET") => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
const [option, setOption] = useState(null);
const postData = (data) => {
setOption({
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
};
useEffect(() => {
const controller = new AbortController();
const fetchData = async (fetchOption) => {
setIsPending(true);
try {
const res = await fetch(url, {
...fetchOption,
signal: controller.signal,
});
if (!res.ok) {
throw new Error(res.statusText);
}
const data = await res.json();
setIsPending(false);
setData(data);
setError(null);
} catch (err) {
if (err.name === "AbortError") {
console.log("the fetch was aborted");
} else {
setIsPending(false);
setError("Could not fetch the data");
}
}
};
if (method === "GET") {
fetchData();
}
if (method === "POST") {
fetchData(option);
}
return () => {
controller.abort();
};
}, [url, option, method]);
return { data, isPending, error, postData };
};
I don't know from where the issue came.
The problem was from useFetch file. when I want to do a post request I shoud cheack if the option useState has a value.
Before I was just check if there is a post method:
const [option, setOptions] = useState(null);
if (method === "POST") {
fetchData(option);
}
Know I'm checking if there is a value in option
const [option, setOptions] = useState(null);
if (method === "POST" && option) {
fetchData(option);
}
You basically trying to add a variable that is not a react state variable into the useEffect on update
const [recipes, setReceipies] = useState();
useEffect(async ()=> { const {data} = awawit useFetch("http://localhost:3000/recipes", "POST")
setReceipies(data);
},[])
navigate("/");
},[recipes]);
Or ofc you can navigate all the way from the mounting useEffect
Good Luck
after you save the data, simply add this code
const history = createBrowserHistory()
history.push(`/`)
I have big apps, that use history, and I never had a problem with it.
and I recomend you to use SWR for data-fetching - React Hooks for Data Fetching.
very simple and powerfull tool:
https://swr.vercel.app/
Hi I am trying to learn and implement react context. I have encountered a bug when tryin to update user context.
I have a login page and when a user logs in, the email address and the user name should be updated in the user context.
When I click on submit, null information is stored in the context. But, when I click on submit the second time, I can see that the context is getting updated.
authentication.component.jsx
import Navigation from "../navigation/navigation.component";
import { Button, InputGroup, Form } from "react-bootstrap";
import { UserContext, setUser, setEmail } from "../../contexts/user.context";
import { useContext, useEffect, useState } from "react";
import axios from "axios";
import React from "react";
import "./authentication.styles.scss";
const UserLogon = () => {
const { setUser, setEmail } = useContext(UserContext);
const [emailAddr, setEmailAddr] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
//console.log(emailAddr);
}, [emailAddr, password]);
const updateFormData = () => {
setEmailAddr(emailAddr);
setPassword(password);
console.log("updated");
console.log(emailAddr);
console.log(password);
};
const saveEmail = (event) => {
setEmailAddr(event.target.value);
//console.log(emailAddr);
};
const savePassword = (event) => {
setPassword(event.target.value);
//console.log(password);
};
const verifyUserHandler = (event) => {
event.preventDefault();
const baseURL = `http://localhost:4000/verify_user?email=${emailAddr}&password=${password}`;
axios
.post(baseURL)
.then((response) => {
//console.log("User verified");
if (response.data[2] === "verified") {
console.log("user verified");
var email = response.data[0];
var name = response.data[1];
console.log("email: ", email);
console.log("name: ", name);
setEmail(email);
setUser(name);
} else {
console.log("user auth error");
}
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="auth-container">
<div className="auth-login">
<div className="login-info">
<Form>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
onChange={saveEmail}
/>
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
onChange={savePassword}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Check me out" />
</Form.Group>
<Button variant="primary" type="submit" onClick={verifyUserHandler}>
Submit
</Button>
</Form>
</div>
</div>
</div>
);
};
export default UserLogon;
user.context.jsx
import { createContext, useState, useEffect } from "react";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
userEmail: null,
setUserEmail: () => null,
});
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [userEmail, setUserEmail] = useState(null);
const setUser = (user) => {
console.log("USER: user context before", currentUser);
setCurrentUser(user);
console.log("USER: user context after", currentUser);
};
const setEmail = (email) => {
console.log("EMAIL: user context before", userEmail);
setUserEmail(email);
console.log("EMAIL: user context after", userEmail);
};
const value = { setUser, setEmail, currentUser, userEmail };
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Any help is appreciated.
thank you.
The React state setter function is async which means that at the moment you console.log it's not yet updated in your state : )
Try this to validate:
const setUser = (user) => {
setCurrentUser(user)
console.log({ currentUser, user })
// To better debug wrap vars in { }
}
const setEmail = (email) => {
setUserEmail(email)
console.log({ userEmail, email })
}
More info from docs: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Try removing the intitial values from createContext. I think it might be causing your issue.
import { createContext, useState, useEffect } from "react";
export const UserContext = createContext();
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [userEmail, setUserEmail] = useState(null);
const setUser = (user) => {
setCurrentUser(user);
console.log(currentUser);
};
const setEmail = (email) => {
setUserEmail(email);
console.log(userEmail);
};
const value = { setUser, setEmail, currentUser, userEmail };
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Add useEffect console log to context provider to check when the state is changing.
useEffect(() => {
console.log("User", currentUser);
console.log("Email", userEmail);
}, [currentUser, userEmail]);
As you can see from here it works properly
I am trying to make a simple login/register form authentication with firebase and react, but i keep getting this error when I try to load the page. "TypeError:firebase__WEBPACK_IMPORTED_MODULE_3_.default.auth is not a function"
It's a simple app that contains 3 pages. first should be the login page and then if login details are correct, that should redirect you to the HomePage.
my firebase.js file contains the usual config file. at the end i have export that file like this.
const fireDb = firebase.initializeApp(firebaseConfig);
export default fireDb.database().ref();
I am not quite sure what am I missing here, maybe just a typo? maybe I should have import it some other modules? any help will be much appreciate.
Down below my Login.js code.
Login.js
import React, { useState, useEffect } from "react";
import "./Login.css";
import { useHistory } from "react-router-dom";
import fireDb from "../firebase";
function Login() {
const history = useHistory();
const [user, setUser] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const clearInputs = () => {
setEmail("");
setPassword("");
};
const clearErrors = () => {
setEmailError("");
setPasswordError("");
};
const handleLogin = () => {
clearErrors();
fireDb
.auth()
.signInWithEmailAndPassword(email, password)
.then((auth) => {
history.push("/HomePage");
})
.catch((err) => {
// eslint-disable-next-line default-case
switch (err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
});
};
const handleSignup = () => {
clearErrors();
fireDb
.auth()
.createUserWithEmailAndPassword(email, password)
.then((auth) => {
if (auth) {
history.push("/HomePage");
}
})
.catch((err) => {
// eslint-disable-next-line default-case
switch (err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
});
};
const authListener = () => {
fireDb.auth().onAuthStateChanged((user) => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser("");
}
});
};
useEffect(() => {
authListener();
}, []);
return (
<div className="login">
<div className="login__container">
<h1>Sign-in</h1>
<form>
<h5>E-mail</h5>
<input
type="text"
autoFocus
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<p className="errorMsg">{emailError}</p>
<h5>Password</h5>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<p className="errorMsg">{passwordError}</p>
<button
type="submit"
onClick={handleLogin}
className="login__signInButton"
>
Sign In
</button>
</form>
<button onClick={handleSignup} className="login__registerButton">
Create your Account
</button>
</div>
</div>
);
}
Since you do this:
export default fireDb.database().ref();
You're effectively calling:
fireDb.database().ref().auth()
And if you check the reference documentation, you'll notice that DatabaseReference doesn't have a auth() method.
I recommend exporting the FirebaseApp object instead:
export default fireDb;
And then importing it:
import firebase from "../firebase";
And:
const fireDb = firebase.database().ref();
const fireAuth = firebase.auth();
Alternatively, you can keep your current export and import and get to FirebaseAuth with:
fireDb
.app
.auth()
.signInWithEmailAndPassword(email, password)
...
this worked for me :
import firebase from 'firebase/compat/app'
import "firebase/compat/auth";
...
onSignUp(){
const { email , password , name} = this.state;
console.log(email);
firebase.auth().createUserWithEmailAndPassword(email , password)
.then((result)=>{
console.log(result)
})
.catch((error) => {
console.log(error)
})
}
I'm studying Nextjs, I come from React and a lot seems to be the same but I was lost with the authentication and private routes.
I looked for a lot of codes on the internet, but either they broke, they didn't make sense to me, or they didn't explain my doubts correctly. My scenario is basically:
I have an application
This application has public and private routes
Users need to login to access private routes
Private routes have the same Navbar
My questions are:
How to create private routes using ReactContext.
How to share the same NavBar between pages (Without having to place the NavBar component on each screen)
How to correctly authenticate a user with my own code using preferably ReactContext
How to reset the routes after authentication (The user is unable to return to the login screen if he clicks the back button on the browser)
How to correctly save the JWT token so that it saves the user's session for longer so that he does not need to log in again
My code is working so far, but I'm sure it is horrible and completely flawed.
I have the following files:
_app.js (Root of the Nextjs project)
index.js (Login page)
privateRoute.js (File that verifies if the user is logged in and allows or not his access)
userContext.js (File that saves user information to be accessed by other components and pages)
NavContext.js (File that checks whether someone is logged in to render the NavBar or not)
_app.js
function MyApp({ Component, pageProps }) {
return (
<UserContextFunc>
<NavContext>
<Component {...pageProps} />
</NavContext>
</UserContextFunc>
);
}
export default MyApp;
index.js
export default function App() {
const [loading, setLoading] = React.useState(false);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const { setState } = React.useContext(UserContext);
const router = useRouter();
const login = () => {
setLoading(true);
fetch("/api/auth/login", {
method: "POST",
body: JSON.stringify({
email,
password,
}),
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
setLoading(false);
if (res.status === 200) {
res.json().then(({ token, roles }) => {
setState({ roles, token });
window.localStorage.setItem(
"rou",
btoa(unescape(encodeURIComponent(JSON.stringify(roles))))
);
window.localStorage.setItem("token", token);
router.replace("/app/adm/home");
});
}
})
.catch((err) => {
});
};
return (
<>
<ReactNotification />
<label htmlFor="exampleInputEmail1" className="form-label">
Email
</label>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control"
/>
<label htmlFor="exampleInputPassword1" className="form-label">
Senha
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control"
/>
<button
onClick={() => login()}
type="button"
className="btn btn-primary btn-block"
>
{loading ? (
<div
className="spinner-border text-light spinner-border-sm"
role="status"
>
<span className="visually-hidden"></span>
</div>
) : (
"Login"
)}
</button>
</>
);
}
privateRoute.js
const indexPage = "/";
const withAuth = (Component) => {
const Auth = (props) => {
const { setState } = React.useContext(UserContext);
const router = useRouter();
React.useEffect(() => {
const token = window.localStorage.getItem("token");
var roles = window.localStorage.getItem("rou");
if (roles) {
roles = decodeURIComponent(escape(atob(roles)));
}
if (!token || !roles || token == "undefined") {
window.localStorage.removeItem("token");
window.localStorage.removeItem("rou");
return router.replace(indexPage);
} else {
setState({ roles, token });
}
}, []);
return <Component {...props} />;
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
userContext.js
export const UserContext = React.createContext();
export const UserContextFunc = ({ children }) => {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'SET':
return {
...prevState,
...action.newState,
};
}
},
{
roles: []
}
);
const setState = newState => {
dispatch({ type: 'SET', newState });
}
const getState = async () => {
return state
}
return (
<UserContext.Provider value={{ getState, setState, state }}>
{children}
</UserContext.Provider>
);
}
NavContext.js
function NavContext(props) {
const { state } = React.useContext(UserContext);
return (
<>
{state.roles && state.token && <NavBar />}
{props.children}
</>
);
}
export default NavContext;
In private files I export them this way
import withPrivateRoute from "../../../utils/privateRoute";
...
export default withPrivateRoute(Dashboard);
I hope I managed to explain it well, I know it's a lot, but I didn't find any content explaining how to create a private route in Nextjs or how to authenticate correctly without using the authentication templates found in the Next documentation.
This code works, but as I said it seems completely wrong. I accept tips too.
I am just taking my first steps with react and redux.
I started the project first without redux and now I have decided to implement it with redux.
The login worked before I adapted it to redux.
ThunkMiddleware is applied
Now the problem:
When I click the login button, the logger or DevTools only shows LOGIN_FAILURE. The page reloads and displays the login again.
If I change this
onSubmit={() => props.login(username, password)}
to this
onSubmit={props.login(username, password)}
LOGIN_REQEST actions are spammed and finally (if the password is stored in the browser) LOGIN_SUCCESS. I get the actual content with correct data from the server.
What do I have to change to make the login work normally?
Thanks for your help
LoginComponent:
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
<div>
<form onSubmit={() => props.login(username, password)}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
const mapDispatchToProps = dispatch => {
return {
login: (username, password) => dispatch(login(username, password))
}
};
export default connect(
null,
mapDispatchToProps
)(Login)
LoginAction
import {
LOGIN_FAILURE,
LOGIN_REQUEST,
LOGIN_SUCCESS
} from "./LoginTypes";
export const login = (username = '', password = '') => {
return (dispatch) => {
dispatch(loginRequest());
axios.post(`server`, {
//data
}).then(
(res) => {
dispatch(loginSuccess(res));
},
(err) => {
dispatch(loginFailure(err.message));
}
);
}
};
export const loginRequest = () =>{
return {
type: LOGIN_REQUEST
}
};
export const loginSuccess = tabs =>{
return {
type: LOGIN_SUCCESS,
payload: tabs
}
};
export const loginFailure = error =>{
return {
type: LOGIN_FAILURE,
payload: error
}
};
LoginReducer:
const LoginReducer = (state = initialState, action) => {
switch (action.type){
case LOGIN_REQUEST:
return {
...state,
loading: true
};
case LOGIN_SUCCESS:
let tabBars = populateArray1(action.payload);
let navIcons = populateArray2();
return{
...state,
loading: false,
tabBars: tabBars,
navIcons: navIcons,
isLoggedIn: true
};
case LOGIN_FAILURE:
return{
...state,
loading: false,
error: action.payload
};
default: return state;
}
};
component, which controls login and content:
function Main(props) {
if(props.auth){
return(
<NotLogin />
)
}
else{
return <Login />
}
}
Your login page is getting refresh/redirecting due to which its not handling the api request & its response properly. Please try this by updating your login component.
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleLogin = (event) => {
event.preventDefault()
props.login(username, password);
}
return (
<div>
<form onSubmit={handleLogin}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
After updating that, please make sure that you are getting correct value in props.auth through redux in your Main component. The Main component should have redux connection with redux auth state in your code.