Why is UseEffect not launching the entire async function? - reactjs

I am using Auth0 on a react application for authentication. After authenticating, I am trying to save the data from authentication to the database (I'm using node.js and mongoose/mongodb). I created a function that makes a post request and also accesses a token with an Auth0 function. When I execute the function in useEffect, the token gets created, as I can see it in the network tab on the Chrome debugger. But, the response api call never gets made. Do you know why this is? Here is my code:
const [data, setData] = useState({
message: '',
error: null,
loading: true
});
const [userData, setUserData] = useState(null);
const serverUrl = 'http://localhost:5000';
const loginOrCreateUser = useCallback(async () => {
try {
const token = await getAccessTokenSilently();
const response = await fetch(`${serverUrl}/users/login`, {
headers: {
Authorization: `Bearer ${token}`,
},
method: "POST",
body: {
name: user.name,
email: user.email,
_id: user.sub
}
});
const responseData = await response.json();
setUserData(responseData);
console.log(userData);
} catch (error) {
setData({...data, error: error.error});
}
});
useEffect(()=> {
loginOrCreateUser();
}, []);
return (
isAuthenticated && (
<div>
<img src={user.picture} alt={user.name} />
<h2>{user.name}</h2>
<Button onClick={callApi}>Call API</Button>
<p>{JSON.stringify(data.message)}</p>
</div>
)
);
}

Related

How to apply authentication with passport-google-oauth in a mern app with json web token

Recently i am working for a organization. they told me use oauth with jwt, no local authentication. i used passport-google-oauth20 and jsonwebtoken package. I am having problem in frontend react part. how to to get the data from backend with fetch or axios?
here is my code
server.js
app.get(
"/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"],
})
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { session: false }),
generateAccessToken,
function (req, res) {
if (req.token) {
const email = req.user.email;
const token = req.token;
return res.status(200).json({ email, token });
} else res.status(400).json({ message: "Something went wrong" });
}
);
token.js
exports.generateAccessToken = (userId) => {
const token = jwt.sign({ _id: userId }, process.env.JWT_SECRET, {
expiresIn: "1h",
issuer: process.env.JWT_ISSUER,
});
return token;
};
Uselogin hook
export const useLogin = () => {
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(null);
const { dispatch } = useAuthContext;
const login = async () => {
setIsLoading(true);
setError(null);
const response = await fetch("localhost:4000/auth/google/callback", {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
});
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", payload: json });
setIsLoading(false);
}
};
return { login, isLoading, error };
};
how can i get the user mail and token from the backend?
I know i can access the page through window.open("localhost:4000/auth/google,"_self"), but then how can i retrive the data.

Not able to implement data from one api used to get data from another

I am making a meme sharing app. In that app there are total 2 apis of getting memes.
One for memes by all the users another is only for individual user.
In second api I am able to get the data as the user id is from 3rd api.
from here i get the id of each individual.
function UserProfile({memeid}) {
const token = localStorage.getItem("token");
const [response, setResponse] = useState({});
const [id, setId] = useState('')
const userData = async() => {
await axios
.get("http://localhost:8081/userInfo/me", {
headers: { Authorization: `Bearer ${token}` },
Accept: "application/json",
"Content-Type": "application/json",
})
.then((res) => {
setResponse(res.data)
setId(res.data.id)
memeid = id
})
.catch((err)=>{
console.log(err)
})
}
console.log(id)
useEffect(()=>{
userData()
},[])
Now I want this to be used in in another api. for that is have written this code.
function MemeById({id}) {
const [response, setResponse] = useState([])
const token = localStorage.getItem("token");
// const id = "632a119672ba0e4324b18c7d"
const memes = async () => {
await axios
.get("http://localhost:8081/memes/" + id, {
headers: { Authorization: `Bearer ${token}` },
Accept: "application/json",
"Content-Type": "application/json",
})
.then((res) => {
const data = res.data;
setResponse(res.data)
console.log(data);
})
.catch((err) => {
alert(err);
console.log(err);
});
};
useEffect(()=>{
memes()
},[])
I am calling these two at User
function User() {
let id;
return (
<div>
<UserProfile memeid={id}/>
<MemeById id = {id} />
</div>
)
}
I am getting the error for this.
How to solve this error
You're making a big mistake. I think you should learn more about state and props in react.
Problem :
In your User component, you're creating a variable and passing that variable into two other component. You're trying to update the value of props from UserProfile and expecting that updated value in MemeById which is not going to work.
Solution :
function User() {
const [memeId, setMemeId] = useState(null);
return (
<div>
<UserProfile updateId={(newId) => setMemeId(newId)}/>
<MemeById memeId = {memeId} />
</div>
)
}
And in your UserProfile component
function UserProfile({updateId}) {
...
const userData = async() => {
...
// memeid = id
updateId(res.data.id)
...
}
In you MemeById component:
function MemeById({memeId}) {
...
// use memeId here
...
}

Checking is user is authenticated with JWT, express and React on backend

I have a problem, I am using Express + React with Typescript. My question is about authentication. I would like to send the token to the backend and respond if the user is logged in or not, on every request. Is there a better way than this included in Frontend, because I need to do the same logic for every private route. I know that I can make checking authentication functions reusable, but I still need to put the logic for rendering pages conditionally. I have one more idea to solve that with wrapper but because of the asynchronous nature of setting states in react I get isAuth false and I am navigated to the "/" every time.
Backend:
export const isAuth = async (req: Request, res: Response) => {
const token = req.get('Authorization')?.split(' ')[1];
if (token) {
try {
const decodedToken = jwt.verify(token, `${process.env.SECRET_KEY}`);
if (decodedToken) res.status(200).json({ isAuth: true });
} catch (e) {
res.status(403).json({ isAuth: false });
}
} else res.status(403).json({ isAuth: false });
};
Frontend
const AddProductPage = () => {
const [isAuth, setIsAuth] = useState<boolean>(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
};
isAuth();
}, []);
return isAuth ? <AddProduct /> : <Navigate to="/" />;
};
Wrapper approach
interface wrapperProps {
children: React.ReactNode;
}
const IsAuth: React.FC<wrapperProps> = (props) => {
const [isAuth, setIsAuth] = useState(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
};
isAuth();
}, []);
console.log('ISAUTH', isAuth);
return isAuth ? <div>{props.children}</div> : <Navigate to="/" />;
};
my solution which seemed to me the most reusable was the wrapper idea which I implemented like this, using the Loading state which allowed me to wait for the asynchronous operation. Do not hesitate to share your opinions/ideas. :)
const IsAuth = (props: wrapperProps) => {
const [isAuth, setIsAuth] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
setIsLoading(false);
};
isAuth();
}, []);
useEffect(() => {
setIsLoaded(!isLoading && isAuth);
}, [isAuth, isLoading]);
if (isLoaded) return <div>{props.children}</div>;
if (!isLoading && !isAuth) return <Navigate to="/" />;
return <div>Loading</div>;
};
export default IsAuth;
If you are using Express.js as your back-end web framework I recommend using express-jwt middleware which checks the Authorization header on every request and basically replaces your isAuth function you wrote.
As a side note, you should return a 401 Unauthorized status code and not a 403 Forbidden. express-jwt will do this for you.
Then, on the front-end, you would perform requests with the token as you did with fetch. But would need to check for 401 errors (and potentially other errors) to decide what to render.
With this approach you would not need to make 2 network calls to the server each time you want communicate, which can cause not only poor performance but also unexpected bugs.

How to create a global 401 unauthroized handler using React + ReactQuery + Axios stack?

So I architected frontend in the way which encapsulates every API operation tied to a single resource inside custom hook like this:
export default function useSubjects() {
const queryClient: QueryClient = useQueryClient();
const token: string | null = useStore((state) => state.user.token);
const { yearCourseId } = useParams<{ yearCourseId: string }>();
const getSubjects = async () => {
const response = await axios.get(`yearCourses/${yearCourseId}/subjects`, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const postSubject = async (subject: SubjectType) => {
const response = await axios.post(`yearCourses/${yearCourseId}/subjects`, subject, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const query = useQuery(SUBJECTS_QUERY_KEY, getSubjects);
const postMutation = useMutation(postSubject, {
onSuccess: (subject: SubjectType) => {
queryClient.setQueryData(SUBJECTS_QUERY_KEY, (old: any) => [...old, subject]);
},
});
return { query, postMutation };
}
Now what is the way to globally handle 401 unauthorized? I would like to navigate user to /login on every unauthorized request. Note that I have more hooks like this tied to other resources.
use the onError callback. You can also do this globally as a callback on the queryCache
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: error => {
// check for 401 and redirect here
}
})
})

how to get user id after token authentication - REACT

I'm new at coding so hoping this is something simple. I am trying to create delete/update functions for the user when they go to their profile. To delete and update my api needs the user id for:
fetch(${process.env.REACT_APP_API_URL}users/${id}/,
Token authentication works and is stored in the localhost but I can't seem to access the id of the user after login. How can I store the id and call it in this function?
**Notes: the 'userData' const was an attempt at calling the api by:
fetch(${process.env.REACT_APP_API_URL}users/${userData.id}/,
(didn't work)
my ProfilePage code:
const Profile = () => {
const [userData, setUserData] = useState({
username: "",
email: "",
password: "",
});
const { id } = useParams();
const navigate = useNavigate();
const EditAccount = () => {
navigate("/edit-account");
};
const addCar = () => {
navigate("/home");
};
useEffect(() => {
fetch(`${process.env.REACT_APP_API_URL}users/${id}/`)
.then((results) => {
console.log("results", results);
return results.json();
})
.then((data) => {
setUserData(data);
});
});
const Logout = () => {
localStorage.clear();
window.location.href = "/";
};
const DeleteUser = async () => {
fetch(`${process.env.REACT_APP_API_URL}users/${id}/`, {
method: "delete",
headers: {
Authorization: `Token ${localStorage.getItem("token")}`,
},
});
navigate("/");
};

Resources