How to save current user data using react-redux? - reactjs

I create simple MERN app. I use passport in authentication. I have home, login, register secret and submit page have. only loginned users can see secret , home and submit pages and only not loginned users can see login and register page. I send data using axios backend. User login,register was succesfully but after loginned users get user data many time after. I dont know how to fix this. My backend doesnt have error. It work well.
-------------- Secret page --------------
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Redirect } from "react-router";
import { user_ } from "../actions/register_action";
const Secret = ()=>{
const dispatch = useDispatch();
const user = useSelector( state => state.user );
useEffect( ()=>{
dispatch( user_ );
},[] );
console.log(user);
if(user === null) return <Redirect to = "/login" />
else
return (
<div className="jumbotron text-center">
<div className="container">
<i className="fas fa-key fa-6x"></i>
<h1 className="display-3">You've Discovered My Secret!</h1>
<p className="secret-text">Jack Bauer is my hero.</p>
<hr />
<a className="btn btn-light btn-lg" href="/logout" role="button">Log Out</a>
<a className="btn btn-dark btn-lg" href="/submit" role="button">Submit a Secret</a>
</div>
</div>
);
}
export default Secret;
-------------- actions --------------
import { fail, user, USER_LOGIN_FAIL, USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS,
USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS
} from "../constants/all";
import axios from "axios";
const user_Register = (email, password) => async (dispatch) =>{
dispatch({ type: USER_REGISTER_REQUEST });
try {
const {data} = await axios( {
method: "POST",
data: {
"username": email,
"password": password
},
withCredentials: true,
url: "http://localhost:3001/register"
});
dispatch({ type: USER_REGISTER_SUCCESS, payload: data });
} catch (error) {
dispatch({type: USER_REGISTER_FAIL, error: error.message});
}
}
const user_Login = (email, password) => async (dispatch) =>{
dispatch({ type: USER_LOGIN_REQUEST });
try {
const {data} = await axios( {
method: "POST",
data: {
username: email,
password: password
},
withCredentials: true,
url: "http://localhost:3001/login"
});
dispatch({ type: USER_LOGIN_SUCCESS, payload: data });
} catch (error) {
dispatch({type: USER_LOGIN_FAIL, error: error.message});
}
}
const user_ = async (dispatch) => {
try{
const {data} = await axios({
method: "GET",
withCredentials: true,
url: 'http://localhost:3001'
});
if(data.isAuth){
dispatch({type:user, payload:data});
}
else{
dispatch({type: fail});
}
}
catch (error){
}
}
export {user_Register, user_Login, user_};
Who have better idea? Thanks a lot!

Can you try re-writing your user_ action creator to be a function that returns a function:
const user_ = () => async (dispatch) => {
try {
const { data } = await axios({
method: "GET",
withCredentials: true,
url: "http://localhost:3001",
});
if (data.isAuth) {
dispatch({ type: user, payload: data });
} else {
dispatch({ type: fail });
}
} catch (error) {}
};
And then in your Secret component's useEffect, call the user_ action creator to create the action:
useEffect(() => {
dispatch(user_());
}, []);
I'm not positive this is the issue, but it does seem like something that should be done either way.

Related

How do I persist my next-auth user session? so i could use the ID provided to fetch data in other routes

What I want to achieve here is, whenever a user logs in, I want to store the data returned because the data holds an ID that I would use to fetch data in other routes.
When a user successfully logs in, he would be redirected to the /home route and the ID gotten from the session would be used to fetch data. Everything works fine initially, but if I refresh the home page, the user becomes null.
This is what my [...nextauth].js looks like.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
export default NextAuth({
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "justin" },
password: {label: "Password",type: "password",placeholder: "******"},
},
async authorize(credentials, req) {
const url = req.body.callbackUrl.split("/auth")[0];
const { username, password } = credentials;
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
},
}),
],
callbacks: {
jwt: ({ token, user }) => {
if (user) {
token.user = user;
}
return token;
},
session: ({ session, token }) => {
if (token) {
session.user = token.user;
}
return session;
},
},
pages: {
signIn: "/auth/login",
newUser: "/auth/register",
},
});
and this is what my /home route looks like
import Card from "#/components/card/Card";
import React, { useEffect, useState } from "react";
import styles from "./home.module.css";
import { Ubuntu } from "#next/font/google";
import { useSession } from "next-auth/react";
import { useDispatch, useSelector } from "react-redux";
const ubuntu = Ubuntu({ weight: "500", subsets: ["cyrillic"] });
const getData = async (id) => {
const res = await fetch({
url: "http://localhost:3000/api/note/getall",
method: "POST",
"content-type": "application/json",
data: {
id: id,
},
});
if (!res.ok) {
console.log(id);
throw new Error("Unable to fetch");
} else {
return res.json();
console.log(res);
}
};
function home() {
const colors = ["#E9F5FC", "#FFF5E1", "#FFE9F3", "#F3F5F7"];
const random = Math.floor(Math.random() * 5);
const rc = colors[random];
const [pop, setPop] = useState("none");
const { user } = useSelector((state) => state.user);
const getDataa = async () => {
console.log(user)
const data = await getData(user._id);
console.log(data);
};
useEffect(() => {
if (user) {
alert(user)
}
}, []);
return (
<div className={styles.home}>
<header>
<h3 className={ubuntu.className}>
Hello, <br /> {user?.username}!
</h3>
<input type="text" placeholder="search" />
</header>
<div className={styles.nav}>
<h1 className={ubuntu.className}>Notes</h1>
</div>
<div className={styles.section}>
<div className={styles.inner}>
{/* {data &&
data.map((e) => (
<Card
rawData={e}
color={colors[Math.floor(Math.random() * colors.length)]}
/>
))} */}
</div>
</div>
<div className="new"></div>
</div>
);
}
export default home;
Add this component to your App.js file :
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return <div>Loading ...</div>;
}
return children;
}
Now in your App function instead of returning <Component {...pageProps} /> you check first if the component has auth property, so you wrapp it with <Auth> to ensure that every component that requires session will only mount when the session finishes loading (that's why the user is null because the session is still loading)
{
Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
);
}
finally you add .auth = {} to every page in whitch you want the session to be defined (Home in your case)
const Home = () => {
//....
}
Home.auth = {};
This also helps to redirect user to /sign-in page if the session is expired
This code seems like it would create a problem / race-condition since you're mixing two different async promise handling styles:
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
})
.then((res) => {
return res.data;
})
.catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});
return user;
It should either be this:
try {
const user = await axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
});
return user.data;
} catch (err) {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
}
Or this:
axios({
url: `${url}/api/user/login`,
method: "POST",
data: {
username: username,
password: password,
},
"content-type": "application/json",
}).then((res) => {
return res.data;
}).catch((err) => {
if (err.response.data) {
throw new Error(err.response.data);
} else {
return null;
}
return null;
});

Error Cannot set headers after they are sent to the client

I got this error whenever I try to log In using Google Login API
In my console I get this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
and in the screen I get white page with this error:
InternalOAuthError: Failed to fetch user profile
I'm using two Login method, one is normal and one using Passport JS
Login.jsx Login Page
import { useEffect, useContext, useRef } from "react";
import { Context } from "../../context/Context";
import axios from "axios";
import { useState } from "react"
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { dispatch, isFetching } = useContext(Context);
const [error, setError] = useState(false);
// FOR LOGIN
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
// FOR GOOGLE LOGIN
useEffect(() => {
fetch(`http://localhost:4000/login/success`, {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
},
})
.then((response) => {
dispatch({ type: "LOGIN_START" });
if (response.status === 200) return response.json();
throw new Error('failed to authenticate user');
})
.then((responseJson) => {
dispatch({ type: "LOGIN_SUCCESS", payload: responseJson.data });
})
.catch((error) => {
dispatch({ type: "LOGIN_FAILURE" });
// eslint-disable-next-line no-console
console.error("Failed to authenticate user", error)
});
}, []);
const google = () => {
window.open("http://localhost:4000/auth/google/callback", "_self");
};
return()
}
auth.js Route:
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
cookies: req.cookies
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});

how to call function after async task react

i want to navigate to dashboard after login and dashboard is protected route
const handleLogin = (e) => {
e.preventDefault();
if (email || password) {
dispatch(loginUser({ email, password }));
navigate("/dashboard");
} else {
toast.error("Please Enter Email and Password");
}
};
i am using redux toolkit createAsyncThunk for api request
export const loginUser = createAsyncThunk("auth/login", async (userDetails) => {
try {
const { email, password } = userDetails;
const res = await fetch("http://localhost:5000/api/users/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const result = await res.json();
if (result.error) {
toast.error(result.error);
} else {
toast.success("Login successfull");
localStorage.setItem("user", JSON.stringify(result));
return result;
}
} catch (error) {
console.log(error);
}
});
when i click on login it try to navigate the page before the state update what i want the navigate function wait untill the api respone recieve then navigate to dashboard
dispatch(loginUser({ email, password })); returns a promise, you can wait for the promise to resolve before doing additional work:
const handleLogin = () => {
dispatch(loginUser({ email, password })).then(() => {
navigate("/dashboard");
})
}
see Unwrapping Result Actions

FIrebase Google Auth Logout Error Connection refused

I am trying to implment google auth into my firebase project. I am succesully able to login into the app but the problem happens at logout.
I receive the error
GET http://localhost:4000/auth/logout net::ERR_CONNECTION_REFUSED
This seems to be an issue with my logout route which is defined in the code as this
export default function Dashboard() {
const [error, setError] = useState("");
const history = useHistory();
async function handleLogout() {
setError("");
axios({
method: "GET",
withCredentials: true,
url: "/auth/logout",
})
.then((res) => {
Auth.deauthenticateUser();
history.push("/login");
})
.catch((err) => {
console.log(err.response.data.message);
});
}
return (
<div>
{error && <p>{error}</p>}
<h2>Home</h2>
<p>Signed In</p>
<button variant="link" onClick={handleLogout}>
Log Out
</button>
</div>
);
}
I think there is an issue with my logout route, any help would be appreciated.
Login Code
function onGoogleSubmit(e) {
e.preventDefault();
var provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
prompt: 'select_account'
})
firebase
.auth()
.signInWithPopup(provider)
.then((result) => {
setError("");
console.log(result.user);
Auth.authenticateUser();
history.push("/");
})
.catch((err) => {
setError(err.message);
});
}
return (
<div className="login-container">
<div className="login-shadow-box">
<button onClick={onGoogleSubmit}>Google</button>
</div>
</div>
</div>
);
axios({
method: "GET",
withCredentials: true,
url: "/auth/logout",
})
Axios will make a GET request at https://domain.tld/auth/logout. I'm not sure if that is server. But you can simply use signOut method to logout.
async function handleLogout() {
setError("");
firebase.auth().signOut()
.then((res) => {
Auth.deauthenticateUser();
history.push("/login");
})
.catch((err) => {
console.log(err.response.data.message);
});
}

How to Resolve 401 unAuthorized error even though Tokens and everything is set? and postman returns no issue at all while testing -MERN Stack

i am new to react and I've been trying to make this api work,(following you tube tutorial), when i test the routes via postman the response i receive is 'OK-working' but then when i try to login from my react app, it turns to unauthorized, i don't know how to deal with this *** i have done everything i could possibily upto my understanding ***, I've explored as much stack Overflow as i could as well as Googled it but still not found something understandable enough.
p.s this is very critical issue for me as this is part of my web Class at college and it holds a great deal of grades!
Attaching the link to github directory as well in case you need it :
https://github.com/nescafestar/twitter-clone
this is my AuthActions.js file
```
//authAction.js
import axios from 'axios';
import {GET_ERRORS, SET_CURRENT_USER} from '../constants';
import setAuthHeader from '../utils/setAuthHeader'
export const registerUser = (userData, history) => dispatch => {
axios.post('http://localhost:5000/api/users/register', userData,{
headers: {
'Authorization': 'Bearer'+ localStorage.getItem('jwtToken')
}
})
.then(res => {
console.log('----> registering')
const { token } = res.data
localStorage.setItem('jwtToken', token)
if(token){
axios.defaults.headers.common['Authorization']='Bearer : '+ token
}
history.push('/')})
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}))
}
export const loginUser = (userData) => dispatch => {
axios.post('http://localhost:5000/api/users/login',userData,{
headers: {
'Authorization': 'Bearer : ' +localStorage.getItem('jwtToken')
}
})
.then(res => {
// console.log(userData)
const { token } = res.data
localStorage.setItem('jwtToken', token)
if(token){
axios.defaults.headers.common['Authorization']='Bearer : ' +token
}
console.log('---> hit dispatch')
dispatch(getCurrentUser())
})
.catch(err => {
// console.log(err);
// dispatch({
// type: GET_ERRORS,
// payload: err.response.data
// })
})
}
export const getCurrentUser = () => dispatch => {
axios.get('http://localhost:5000/api/users',{
headers: {
'Authorization': 'Bearer : ' +localStorage.getItem('jwtToken')
}
})
.then(res => dispatch(setCurrentUser(res.data)))
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}))
}
export const setCurrentUser = (data) => {
console.log('----> setting user!')
return {
type: SET_CURRENT_USER,
payload: data
}
}
export const logoutUser=()=>dispatch=>{
localStorage.removeItem('jwtToken')
setAuthHeader()
dispatch(setCurrentUser())
}
```
this is the set Auth Header one:
```
import axios from 'axios'
// const jsonwebtoken=require('jsonwebtoken')
export default function(token){
console.log(token)
if(token){
console.log('Token has been set-properly')
return axios.defaults.headers.common['Authorization']='Bearer'+ token
}else{
return axios.defaults.headers.common['Authorization']=null
}
}
```
and lastly this is my POST.js file which is also returning the same issue of being unAuthorized
import axios from 'axios'
// const jsonwebtoken=require('jsonwebtoken')
export default function(token){
console.log(token)
if(token){
console.log('Token has been set-properly')
return axios.defaults.headers.common['Authorization']='Bearer'+ token
}else{
return axios.defaults.headers.common['Authorization']=null
}
}
``` ```
////////////////////////*** ROUTER ***
``` ```
// also the Routes/User.js file in case someone wants to know about it as well
const router = require("express").Router();
const User = require("../models/User");
const jwt=require('jsonwebtoken');
const passport = require("passport");
//validation
const validateRegisterInput = require("../validation/register");
const validateLoginInput = require("../validation/login");
//handling password hasing
const bcrypt = require("bcryptjs");
// const utils = require("../utils/utils");
//routing requests
//handling post request
router.route("/register").post((req, res) => {
const { isValid, errors } = validateRegisterInput(req.body);
if (!isValid) {
return res.status(404).json(errors);
}
//test arguments
// return res.send('OKKKK!!');
//register user is it does not exist in db
User.findOne({ email: req.body.email }).then((user) => {
if (user) {
errors.email = "Email already Registered!";
return res.status(404).json(errors);
}
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(req.body.password, salt, function (err, hash) {
//adding new User to DB is Not Exist
const newMember = new User({
username: req.body.username,
email: req.body.email,
password: hash,
});
newMember
.save()
.then((newMember) => res.json(newMember))
.catch((err) => console.log(err));
});
});
});
}); //register route ends
//Login Route
router.route('/login')
.post((req, res) => {
const { errors, isValid } = validateLoginInput(req.body)
if (!isValid) {
return res.status(404).json(errors)
}
User.findOne({ email: req.body.email })
.then(user => {
if (!user) {
errors.email = 'User not found/not exist'
return res.status(404).json(errors)
} else {
bcrypt.compare(req.body.password, user.password)
.then(isMatch => {
if (isMatch) {
const token = jwt.sign({ id: user._id }, process.env.SECRET, { expiresIn: '1d' }, function (err, token) {
return res.json({
success: true,
token: token
})
})
} else {
errors.password = 'Password is incorrect'
return res.status(404).json(errors)
}
})
}
})
})
//returning route for logged in user
router.route('/')
.get( passport.authenticate('jwt', { session: false }),(req, res) => {
console.log('here! in home redirect')
res.json({
_id: req.user._id,
email: req.user.email,
username: req.user.username,
followers: req.user.followers,
following: req.user.following
})
})
module.exports = router;
You are not populating the Authorization header consistently or correctly. Assume your jwtToken value is AAAA for simplicity. In some cases you send BearerAAAAAA, in others you send Bearer : AAAA. The standard should be to use the string "Bearer", followed by one space, followed by the base64 encoding of the token (Bearer AAAA). See the RFC for more information: https://www.rfc-editor.org/rfc/rfc6750
When setting the header, you could use something like this:
const token = localStorage.getItem('jwtToken') //Or however you choose to get it
const headers = {
Authorization: `Bearer ${token}`
}

Resources