Can't figure out an efficient way to set a user's profile and then pass that data onwards to other components as needed.
Below is an example of my current logic, and although the app works, the rendering is not efficient. When I click on various parts of my app, the data coming from my UserProfile component is re-rendering every time causing the text to change from the initial text to the rendered data text.
The main issue, I believe, is the communication between the UserProfile and Dashboard Home snippets below. I'm new to the useEffect logic, so I would imagine the inefficient setup is with that hook.
Any help or nudge in the right direction is appreciated!
Thanks
AuthContext file => Setting the current user
import React, { useContext, useState, useEffect } from 'react';
import firebase from 'firebase/app';
import {
auth,
signInWithGoogle,
createUserProfileDocument,
firestore,
} from '../firebase.utils';
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password);
};
const logout = () => {
setCurrentUser(null);
return auth.signOut();
};
const resetPassword = email => {
return auth.sendPasswordResetEmail(email);
};
const updateEmail = email => {
return currentUser.updateEmail(email);
};
const updatePassword = password => {
return currentUser.updatePassword(password);
};
const deleteProfile = () => {
currentUser.delete();
firestore.doc(`users/${currentUser.uid}`).delete();
};
const updateName = displayName => {
return currentUser.updateProfile({
displayName: displayName,
});
};
const setName = displayName => {
return auth.currentUser.updateProfile({
displayName: displayName,
});
};
const googleSignIn = () => {
const google = signInWithGoogle();
setCurrentUser(google);
return google;
};
const updatePersonalSettings = data => {
createUserProfileDocument(currentUser, data);
};
const updateAccountSettings = data => {
createUserProfileDocument(currentUser, data);
};
console.log(currentUser);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
updateName,
setName,
googleSignIn,
updatePersonalSettings,
updateAccountSettings,
deleteProfile,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
UserProfile file => Setting the userInfo
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
const setUserData = async () => {
if (currentUser) {
const userRef = await createUserProfileDocument(currentUser);
userRef.onSnapshot(doc => {
setUserInfo({
id: doc.id,
...doc.data(),
});
});
}
};
useEffect(() => {
setUserData();
}, []);
return { userInfo };
};
export default UserProfile;
Dashboard home file => Example of rendering data from the UserProfile component
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import sprite from '../../../../assets/sprite.svg';
import UserProfile from '../../../../user-profile/user-profile';
import './home-dashboard.styles.scss';
const HomeDashboard = () => {
const { userInfo } = UserProfile();
const handleCurrentLevel = () => {
return !userInfo || userInfo.currentLevel === undefined ? (
<h1>Welcome! Start your eval to see your level</h1>
) : (
<h1>Current Level: {userInfo.currentLevel}</h1>
);
};
const handleCurrentLevelCard = () => {
return !userInfo || userInfo.currentLevel === undefined
? 'Start a new eval to see your level'
: `You scored a ${userInfo.currentLevel} in your last eval`;
};
return (
<div className="home-dash">
<div className="home-dash__title">{handleCurrentLevel()}</div>
<div className="home-dash__cards">
<div className="home-dash__card-1">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-success'}></use>
</svg>
<h3 className="home-dash__card-title">
Latest Eval Results
</h3>
<div className="home-dash__card-result-text">
{handleCurrentLevelCard()}
</div>
<Link to="/eval-quiz">
<button className="home-dash__card-btn--purple">
Start New Eval
</button>
</Link>
</div>
{/* TODO Add resutls to firestore */}
{
<div className="home-dash__card-2">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-lightbulb'}></use>
</svg>
<h3 className="home-dash__card-title">
Areas to practice
</h3>
<div className="home-dash__card-result-text">
We recommend working on reading skills
</div>
{/*<button className="home-dash__card-btn--blue">
Practice
</button>*/}
<div className="home-dash__coming-soon">
Coming soon!
</div>
</div>
}
</div>
</div>
);
};
export default HomeDashboard;
Firestore/Firebase setup
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
});
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = await userRef.get();
const { displayName, email, photoURL } = userAuth;
const createdAt = new Date();
if (!snapShot.exists) {
console.log(displayName);
try {
await userRef.set({
displayName,
photoURL,
email,
createdAt,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
if (snapShot.exists) {
try {
await userRef.update({
displayName,
email,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
return userRef;
};
export const firestore = firebase.firestore();
const googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.setCustomParameters({ prompt: 'select_account' });
export const signInWithGoogle = () => auth.signInWithPopup(googleProvider);
export const auth = app.auth();
export default app;
Consider this answer a draft, I've made it public in case you can piece it together but I'll update it with some documentation in the morning.
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!currentUser) {
// user is signed out
setUserInfo(null);
setLoading(false);
return;
}
const userRef = /* ... */;
setUserInfo(undefined); // clear stale data
setLoading(true); // update loading status
return userRef.onSnapshot({
next(snapshot) {
if (!snapshot.exists) {
userRef
.set({
/* new data */
})
.catch((err) => {
// TODO: Handle errors
});
return;
}
setUserInfo({
id: snapshot.id,
...snapshot.data(),
});
setLoading(false);
},
error(err) {
// TODO: Handle errors
}
});
}, [currentUser]);
return { userInfo }; // <-- this seems odd? missing loading?
};
export default UserProfile;
Related
I have a problem when I want to log in to the login by entering the email and password. What happens is that when I enter with the correct email and correct password, the animation appears but it stays cycled, and if I refresh the page and try again, now it lets me enter into the application
Here's my login form code:
import axios from "axios";
import { useRef, useState } from "react";
import { storeToken } from "../utils/authServices";
import { useNavigate } from "react-router-dom";
import { useLoading } from "../context/hooks/useLoading";
import { LoginForm } from "../components";
export const Login = () => {
const API_URL = "https://api.app"; //I hide the API for security reasons
const { run } = useLoading();
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const correoRef = useRef("");
const passwordRef = useRef("");
const handleSubmit = async (e) => {
e.preventDefault();
const { value: correo } = correoRef.current;
const { value: password } = passwordRef.current;
await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
})
.then((response) => {
storeToken(response.data.token);
run();
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
})
.catch((err) => {
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
});
};
return (
<LoginForm
correoRef={correoRef}
passwordRef={passwordRef}
handleSubmit={handleSubmit}
error={error}
errorMessage={errorMessage}
/>
);
};
import { createContext, useReducer, useContext } from "react";
const initialState = {
loading: false,
alerts: [],
};
const reducers = (state, action) => {
switch (action.type) {
case "LOADING_RUN":
return {
...state,
loading: true,
};
case "LOADING_STOP":
return {
...state,
loading: false,
};
default:
return { ...state };
}
};
const AppContext = createContext();
const AppContextProvider = (props) => {
const [state, dispatch] = useReducer(reducers, initialState);
return <AppContext.Provider value={{ state, dispatch }} {...props} />;
};
const useAppContext = () => useContext(AppContext);
export { AppContextProvider, useAppContext };
import { useMemo } from "react";
import { useAppContext } from "../AppContext";
export const useLoading = () => {
const { dispatch } = useAppContext();
const loading = useMemo(
() => ({
run: () => dispatch({ type: "LOADING_RUN" }),
stop: () => dispatch({ type: "LOADING_STOP" }),
}),
[dispatch]
);
return loading;
};
import jwt_decode from "jwt-decode";
export const storeToken = (token) => {
localStorage.setItem("token", token);
};
export const getToken = (decode = false) => {
const token = localStorage.getItem("token");
if (decode) {
const decoded = jwt_decode(token);
return decoded;
}
return token;
};
export const logout = () => {
localStorage.removeItem("token");
};
How can I log in without refreshing the page?
There's two problems here. One is you're using await with a .then .catch block. Pick one or the other. You're also never calling the stop() dispatch when your async call is complete which appears to be responsible for removing the loader.
Instead of:
const { run } = useLoading();
Use:
const { run, stop } = useLoading();
Then change this:
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
To this:
setTimeout(() => {
navigate("/nueva-solicitud");
stop();
}, 1000);
Although I would just recommend writing the entire promise like this:
try {
run();
const response = await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
});
storeToken(response.data.token);
navigate("/nueva-solicitud");
stop();
} catch (err) {
stop();
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
}
I have this custom http hook with abort when you try to go to a different page (I saw it in a tutorial but I am not truly sure I need it). When I fetch data with it and useEffect(), I have this error on the backend but the request is executed and everything is as planned. My question is, how to improve my code so it does not throw this error and do I need this functionality with abortController() ?
http-hook.ts
import { useCallback, useRef, useEffect } from "react";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { selectError, showError } from "src/redux/error";
import { selectLoading, startLoading, stopLoading } from "src/redux/loading";
export const useHttpClient = () => {
const dispatch = useDispatch();
const error = useSelector(selectError);
const loading = useSelector(selectLoading);
const activeHttpRequests: any = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
dispatch(startLoading());
const httpAbortCtrl = new AbortController();
activeHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal,
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortCtrl
);
if (!response.ok) {
throw new Error(responseData.message);
}
dispatch(stopLoading());
return responseData;
} catch (err) {
dispatch(showError(err.message));
dispatch(stopLoading());
throw err;
}
},
[]
);
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((abortCtrl: any) => abortCtrl.abort());
};
}, []);
return { loading, error, sendRequest };
};
UserInfo.tsx
import React, { Fragment, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useHttpClient } from "src/hooks/http-hook";
import classes from "./UserInfo.module.css";
const UserInfo = () => {
const { sendRequest } = useHttpClient();
const [currentUser, setCurrentUser] = useState<any>();
const userId = useParams<any>().userId;
useEffect(() => {
const fetchCurrentUser = async () => {
try {
const responseData = await sendRequest(
`http://localhost:5000/api/user/${userId}`
);
setCurrentUser(responseData.user);
console.log("currentUser ", currentUser);
} catch (err) {
console.log(err);
}
};
fetchCurrentUser();
}, [sendRequest ,userId]);
return currentUser ? (
<Fragment>
<div className={classes.cover} />
<div className={classes.user_info}>
<img
alt="user_img"
src={`http://localhost:5000/${currentUser.image}`}
className={classes.user_img}
/>
<div className={classes.text}>
<p>
Name: {currentUser.name} {currentUser.surname}
</p>
<p>Email: {currentUser.email}</p>
<p>Age: {currentUser.age}</p>
</div>
</div>{" "}
</Fragment>
) : (
<p>No current user</p>
);
};
export default UserInfo;
Backend
getCurrentUser.ts controller
const getCurrentUser = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const userId = req.params.userId;
let user;
try {
user = await User.findById(userId);
} catch (err) {
const error = new HttpError("Could not fetch user", 500);
return next(error);
}
res.json({ user: user.toObject({ getters: true }) });
};
I have the above firebase db. I want to extract the displayName value and use it in a greeting message after the user is successfully login (e.g. Hello George!). I manage to achieve this but when I refresh the page everything disappears and in console I get this error "index.esm2017.js:1032 Uncaught TypeError: Cannot read properties of undefined (reading 'indexOf')".
Is this a problem of how I extract the displayName from firebase document?
Can someone explain to me what is the problem, please?
Here is my code:
AuthContext.js
import { createContext, useContext, useEffect, useState } from "react";
import {
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { auth } from "../utils/firebase/firebase.utils";
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const signIn = (email, password) =>
signInWithEmailAndPassword(auth, email, password);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser);
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const logOut = () => signOut(auth);
return (
<UserContext.Provider value={{ user, signIn, logOut }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
WelcomePage.jsx
import React, { useState, useEffect } from "react";
import { UserAuth } from "../contexts/AuthContext";
import { db } from "../utils/firebase/firebase.utils";
import { doc, Firestore, getDoc } from "firebase/firestore";
const WelcomePage = () => {
const [userDetails, setUserDetails] = useState({});
const { user } = UserAuth();
useEffect(() => {
const docRef = doc(db, "users", user.uid);
const fetchData = async () => {
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
fetchData();
}, [user]);
return (
<div>
<h1>Hello, {userDetails.displayName}!</h1>
</div>
);
};
export default WelcomePage;
You might only want to fetch documents in the WelcomePage component if there's a truthy uid value to use.
const { user } = UserAuth();
useEffect(() => {
const fetchData = async () => {
const docRef = doc(db, "users", user.uid);
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
if (user?.id) {
fetchData();
}
}, [user]);
I'm building a searchable index of lessons in Next.js for a school using Airtable as my backend, and I am able to pull the initial page of lessons with getStaticProps and display them. I added a search form that passes an array of search terms through context to a search API component, and though I am able to console log the search terms, I think I am passing the searchTerms array incorrectly, or the search API is not parsing them.
I'm using Auth0 so the component are wrapped with auth, and that all works. Just not returning anything from Airtable when I submit the form.
What I suspect is that I am mangling the fetch or not passing the searchTerms array correctly.
// index.js
import Head from "next/head";
import Image from "next/image";
import Navbar from "../components/Navbar";
import Lesson from "../components/Lesson";
import LessonSearchForm from "../components/LessonSearchForm";
import { table, minifyRecords } from "./api/utils/Airtable";
import { lessonsProvider, LessonsContext } from "../contexts/LessonsContext";
import { useEffect, useState, useContext } from "react";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
export default function Home({ initialLessons }) {
const { lessons, setLessons } = useContext(LessonsContext);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const { user, error, isLoading } = useUser();
const [lessonsError, setLessonsError] = useState(null);
useEffect(() => {
setLessons(initialLessons);
}, []);
return (
<div>
<Head>
<title>Lesson Locator</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Navbar user={user} />
<main>
{user && (
<>
<h2 className="text-2xl text-center mb-4">Lesson Locator Search</h2>
<LessonSearchForm />
{lessons &&
lessons.map((lesson) => (
<Lesson key={lesson.id} lesson={lesson} />
))}
</>
)}
</main>
</div>
);
}
export async function getServerSideProps(context) {
try {
const lessons = await table.select({}).firstPage();
return {
props: {
initialLessons: minifyRecords(lessons),
},
};
} catch (err) {
console.error(err);
return {
props: {
err: "Something went wrong!",
},
};
}
}
// LessonSearchForm.js component ( everything before the return statement )
import React, { useState, useContext } from "react";
import { LessonsContext } from "../contexts/LessonsContext";
export default function LessonSearchForm() {
const [lessonTitle, setLessonTitle] = useState("");
const { findLessons } = useContext(LessonsContext);
const [lessonSource, setLessonSource] = useState("");
const [lessonNumber, setLessonNumber] = useState("");
const [textFieldOption, setTextFieldOption] = useState("text");
const [position, setPosition] = useState("");
const [lessonErrorMsg, setLessonErrorMsg] = useState("");
const [isFindingLesson, setIsFindingLesson] = useState(false);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const handleChange = (e) => {
setSearchTerms({
...searchTerms,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log({ searchTerms });
findLessons([searchTerms]);
setLessonErrorMsg("");
setIsFindingLesson(false);
};
// LessonsContext.js
import { createContext, useState } from "react";
const LessonsContext = createContext();
const LessonsProvider = ({ children }) => {
const [lessons, setLessons] = useState([]);
const [searchTerms, setSearchTerms] = useState([]);
const refreshLessons = async () => {
try {
const res = await fetch("/api/getLessons");
const lessons = await res.json();
setLessons(initialLessons);
} catch (error) {
console.log(err);
}
};
const findLessons = async ([searchTerms]) => {
try {
const res = await fetch("/api/searchLessons", {
method: "GET",
// body: JSON.stringify(searchTerms),
// fields: [searchTerms],
headers: { "Content-Type": "application/json" },
});
const foundLessons = await res.json();
console.log(foundLessons);
} catch (error) {
console.error("Error performing search", error);
}
};
return (
<LessonsContext.Provider
value={{
lessons,
setLessons,
refreshLessons,
searchTerms,
setSearchTerms,
findLessons,
}}
>
{children}
</LessonsContext.Provider>
);
};
export { LessonsProvider, LessonsContext };
// searchLessons.js in API (Just trying to parse the Title field for testing)
import { table, minifyRecords, findRecordByFilter } from "./utils/Airtable";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
// export default withApiAuthRequired(async (req, res) => {
const searchLessons = async (req, res) => {
// const { user } = useUser();
// const searchTerms = req.fields;
try {
const records = await table
.select({
filterByFormula: `FIND("${req.title}",{Title})`,
})
.firstPage();
const minifiedRecords = minifyRecords(records);
res.statusCode = 200;
res.json(minifiedRecords);
} catch (err) {
res.statusCode = 500;
res.json({ meg: "something went wrong!" }, err);
}
};
export default searchLessons;
// Airtable.js (Utils for Airtable)
const Airtable = require("airtable");
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
process.env.AIRTABLE_BASE_ID
);
const table = base(process.env.AIRTABLE_TABLE_NAME);
const getMinifiedRecord = (record) => {
if (!record.fields.completed) {
record.fields.completed = false;
}
return {
id: record.id,
fields: record.fields,
};
};
const minifyRecords = (records) => {
return records.map((record) => getMinifiedRecord(record));
};
export { table, getMinifiedRecord, minifyRecords };
i am a newbie to react but i'm learning and need your help here.
I use Auth0 for Authentication and i have implemented their react sample in parts:
https://auth0.com/docs/quickstart/spa/react/01-login
This are parts of my code:
App.js:
<Auth0Provider
domain={AUTH_CONFIG.domain}
client_id={AUTH_CONFIG.clientId}
redirect_uri={AUTH_CONFIG.callbackUrl}
onRedirectCallback={onRedirectCallback}
>
<Router history={history}>
<RequireAuthentication>
<MyTheme>
<MyLayout />
</MyTheme>
</RequireAuthentication>
</Router>
</Auth0Provider>
Auth0Provider:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
import jwtDecode from "jwt-decode";
import axios from "axios";
import AUTH_CONFIG from "./auth0Config";
import { useDispatch } from "react-redux";
import * as authActions from "app/auth/store/actions";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
const initAuth0 = async () => {
console.log("initAuth0 start");
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
const isAuthenticated = await auth0FromHook.isAuthenticated();
console.log("Authenticated from init: " + isAuthenticated);
setIsAuthenticated(isAuthenticated);
setLoading(false);
console.log("initAuth0 end");
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await getUserData();
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await getUserData();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
};
const getAccessToken = async () => {
const accessToken = await auth0Client.getTokenSilently({
audience: AUTH_CONFIG.identity_audience,
scope: "read:allUsers read:UserPermission"
});
return accessToken;
};
const getIdToken = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
const claims = await auth0Client.getIdTokenClaims();
return claims.__raw;
};
const getTokenData = async () => {
const token = await getIdToken();
const decoded = jwtDecode(token);
if (!decoded) {
return null;
}
return decoded;
};
const getUserData = async () => {
console.log("getuserdata");
const tokenData = await getTokenData();
const accessToken = await getAccessToken();
return new Promise((resolve, reject) => {
const { sub: userId } = tokenData;
const UserService =
"https://localhost:44312/api/v1/usermanagement/user/" + userId;
axios
.get(UserService, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS,POST,PUT",
"Access-Control-Allow-Headers":
"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers",
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken
}
})
.then(response => {
resolve(response.data);
})
.catch(error => {
// handle error
console.warn("Cannot retrieve user data", error);
reject(error);
});
});
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
RequireAuthentication:
import React, { useEffect } from "react";
import { useAuth0 } from "app/auth/AuthProvider";
import { SplashScreen } from "#my";
import history from "#history";
export const RequireAuthentication = ({ children }) => {
const { isAuthenticated, loading } = useAuth0();
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, []);
const checkAuth = () => {
console.log("checkAuth isAuthenticated: " + isAuthenticated);
console.log("checkAuth loading: " + loading);
if (!isAuthenticated && !loading) {
history.push("/login");
}
};
return isAuthenticated ? (
<React.Fragment>{children}</React.Fragment>
) : (
<SplashScreen />
);
};
callback.js:
import React, { useEffect } from "react";
import { SplashScreen } from "#my";
import { useAuth0 } from "app/auth/AuthProvider";
function Callback(props) {
const { isAuthenticated, handleRedirectCallback, loading } = useAuth0();
useEffect(() => {
const fn = async () => {
if (!loading) {
console.log("handleRedirectCallback: " + loading);
await handleRedirectCallback();
}
};
fn();
}, [isAuthenticated, loading, handleRedirectCallback]);
return <SplashScreen />;
}
export default Callback;
The problem is that the RequireAuthentication Component is rendered before the Auth0Provider is completely initialized and therefore i get never the isAuthenticated on "true".
The RequireAuthentication Component is a child of the Auth0Provider. Is it possible to wait for the Auth0Provider is fully initialized before rendering the RequireAuthentication Component???
What is the right way here?? Am I completely wrong?
Thanks
Chris
Depend on loading and isAuthenticated items in useEffect so that component will re render once they change.
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, [loading, isAuthenticated]);