how awoid 2 times re render in react-native - reactjs

i have tried to do routing according to user check and i did but my method worked two times. i understand it from my log
i'm using redux and here is my "useeffect" in mainrouter.js
// currentUser : null // in initialState for users
useEffect(() => {
if(states.currentUser== null)
{
console.log("Checking user for routing");
actions.getCurrentUserACT();
}
else{
console.log("Logged in")
}
},[]);
my mainrouter.js return
return (
<NavigationContainer>
{
states.currentUser == null ?
(this.SignStackScreen()) :
(this.MainTabNavigator())
}
</NavigationContainer>
);
here is my "getCurrentUserAct" method in redux actions
export function getCurrentUserACT() {
return function (dispatch) {
firebase.auth().onAuthStateChanged(user => {
if (user) {
console.log("User authed..");
dispatch({type:actionTypes.CURRENT_USER,payload:user})
} else {
console.log("User notauthed..");
dispatch({type:actionTypes.CURRENT_USER,payload:null})
}
});
}
when my app start, logs are .. (if i logged in before)
Checking user for routing
User authed
User authed
i think the reason for this, useeffect worked first time and state changed which use in return and rerender"
but then i removed to condition, logs were same.
so how i can avoid this?
is there a way to run method before from useeffect and avoid second render? or wait data in useeffect?

Your code looks right and there is no reason to log two times because of useEffect because you used it as componentDidMount so it is called one time.
The reason of logging two times is this code snippet:
firebase.auth().onAuthStateChanged(user => {
here onAuthStateChanged is callback and it is called when state of authentication changed on Firebase.
If you are going to check if the user logged in then you need to avoid to do like below
if (user) { }
Please try console.log(user) and check if user keeps same on double logging.
I believe user inner properties will be changed.
Hope this helps you to understand

export function getCurrentUserACT() {
return function (dispatch) {
firebase.auth().onAuthStateChanged(user => {
dispatch(setCurrentUserACT(user))
console.log("control point" + user);
// if (user) {
// console.log("User authed..");
// dispatch({type:actionTypes.CURRENT_USER,payload:user})
// } else {
// console.log("User notauthed..");
// dispatch({type:actionTypes.CURRENT_USER,payload:null})
// }
});
}
Logs were double
Checking user for routing
control point[object Object]
control point[object Object]
______________________Edited 31.05______________________
I change my code a little bit like below
const dispatch = useDispatch();
const currentUser = useSelector(state => state.currentUserReducer);
useEffect(() => {
console.log(JSON.stringify(currentUser));
if (currentUser == null) {
console.log("Checking user for routing");
//dispatch(getCurrentUserACT());
}
else {
console.log("Logged in")
}
}, []);
I found something like hint but I can't fix because i dont know
When app start logs are
null
Checking user for routing
I didnt dispatch getCurrentUserAct(), so logs are right. If i dispatch, user will be checking (user already logged in), component rerender and here is double logs from double render.
I need dispatch before useeffect, not in useeffect
Is there a way?

Related

how to execute a component before another one in next.js?

I've been struggling with this problem for a while. I have an Auth component inside which I try to access to local storage to see if there is a token in there and send it to server to validate that token.
if token is valid the user gets logged-in automatically.
./components/Auth.tsx
const Auth: React.FC<Props> = ({ children }) => {
const dispatch = useDispatch(); // I'm using redux-toolkit to mange the app-wide state
useEffect(() => {
if (typeof window !== "undefined") {
const token = localStorage.getItem("token");
const userId = localStorage.getItem("userId");
if (userId) {
axios
.post("/api/get-user-data", { userId, token })
.then((res) => {
dispatch(userActions.login(res.data.user)); // the user gets logged-in
})
.catch((error) => {
localStorage.clear();
console.log(error);
});
}
}
}, [dispatch]);
return <Fragment>{children}</Fragment>;
};
export default Auth;
then I wrap every page components with Auth.tsx in _app.tsx file in order to manage the authentication state globally.
./pages/_app.tsx
<Provider store={store}>
<Auth>
<Component {...pageProps} />
</Auth>
</Provider>
I have a user-profile page in which user can see all his/her information.
in this page first of all I check if the user is authenticated to access this page or not.
if not I redirect him to login page
./pages/user-profile.tsx
useEffect(() => {
if (isAuthenticated) {
// some code
} else {
router.push("/sign-in");
}
}, [isAuthenticated]);
The problem is when the user is in user-profile page and reloads . then the user always gets redirected to login-page even if the user is authenticated.
It's because the code in user-profile useEffect gets executed before the code in Auth component.
(user-profile page is a child to Auth component)
How should i run the code in Auth component before the code in user-profile page ?
I wanna get the user redirected only when he's not authenticated and run all the authentication-related codes before any other code.
Are you sure that the problem is that user-profile's useEffect is executed before Auth's useEffect? I would assume that the outermost useEffect is fired first.
What most probably happens in your case is that the code that you run in the Auth useEffect is asynchronous. You send a request to your API with Axios, then the useEffect method continues to run without waiting for the result. Normally, this is a good situation, but in your profile, you assume that you already have the result of this call.
You would probably have to implement an async function and await the result of both the axios.post method and dispatch method. You would need something like this:
useEffect(() => {
async () => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem("token")
const userId = localStorage.getItem("userId")
if (userId) {
try {
const resp = await axios.post("/api/get-user-data", {userId, token})
await dispatch(userActions.login(res.data.user)) // the user gets logged-in
} catch(error) {
localStorage.clear()
console.log(error)
}
}
}
}()
}, [dispatch])
I think this should work, but it would cause your components to wait for the response before anything is rendered.

React Native: Run actions after succesfully logged in

I'm creating a React Native app in which some actions like adding to favorites require the user to be logged in.
The problem
If a certain action needs authentication, the following flow is executed:
User tab over the favorite button(Protected action)
A modal(screen with presentation: "modal") is rendered to allow the user to enter their credentials
If the user is successfully logged in, the modal is closed and the user is directed to the screen on which it was located(goBack() navigation action).
THE PROBLEM: user needs to press again over the favorite button, the idea is, if the user is successfully logged in, the action (add to favorites) is executed immediately without the user having to perform the action again.
NOTE: I can have different protected actions on the same screen
Question
how can I request the user to log in to perform the action and have the action executed automatically after successful login?
execute the protected action only once, only when the user logs in successfully and the modal is closed, if the user is already authenticated the protected action should not be executed again.
Example flow
function FavoriteScreen({ navigation }) {
const { isAuthenticated } = useAuth();
if (isAuthenticated) {
addFavorite(productId);
} else {
navigation.navigate("LoginScreen");
}
}
Things I've tried
Send a callback through the parameters(called nexAction) from the protected action screen to the login screen, run the callback after successfully log in, and close the modal, but I'm getting non-serializable warning, and this screen implements deep linking, so I cannot ignore the warning as the documentation suggests.
if (isAuthenticated) {
addFavorite();
} else {
navigation.navigate(NavigationScreens.LoginScreen, {
nextAction: addFavorite,
});
}
Use the focus event to run the protected action after the user is successfully logged in and the modal is closed. This approach has some problems, each time user focuses on the screen and is authenticated, the protected action is going to be run, and there may be more than one protected action on the screen, meaning that they will all be executed when the screen is focused.
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
if (isAuthenticated) {
addFavorite();
}
});
return unsubscribe;
}, [isAuthenticated, navigation, addFavorite]);
I think you're on the right path with useEffect. What about storing the user action when clicking a protected action, and then performing the action and clearing the stored value after the user has logged in?
Something like:
const [pendingAction, setPendingAction] = useState();
user clicks addFavorite action, but is not logged in:
setPendingAction({type: 'addFavorite', value: 12});
Then handling the login in the modal, the previous screen will stay mounted and keep its state.
And then calling the pendingAction in the useEffect:
useEffect(() => {
if (isAuth) {
if (pendingAction) {
switch(pendingAction.type) {
// do something
}
setPendingAction(undefined);
}
} else {
// code runs when auth changes to false
}
}, [isAuth, pendingAction, setPendingAction]);
Have you tried an effect?
// Will run once on mount and again when auth state changes
useEffect(() => {
if (isAuth) {
// code runs when auth changes from false to true
} else {
// code runs when auth changes to false
}
}, [isAuth]);
You can't send a function on nav params, but you can send data. You can try parroting the data back from the login screen and performing your one time action like this:
const Fav = ({ route }) => {
const { isAuthenticated } = useAuth();
const {nextAction} = route.params;
useEffect(() => {
if (isAuthenticated) {
// If login navigated back to us with a nextAction, complete it now
if (nextAction === CallbackActions.ADD_FAVORITE) addFavorite();
} else {
navigation.navigate(NavigationScreens.LoginScreen, {
nextAction: CallbackActions.ADD_FAVORITE, // Send some data to the login screen
});
}
}, []);
};
const Login = ({ route }) => {
const {nextAction} = route.params;
// Do login stuff
login().then(() => {
navigation.navigate(NavigationScreens.FavoriteScreen, {
nextAction, // Send back the caller's nextAction
});
});
};

React Render UI Before Redirect

I am having problem rendering ui before redirect in react. I has a variable is called from api, i want to check if it is 'x' then will redirect. But ui will render before redirect.
Bellow is my code:
useLayoutEffect(() => {
getProfile().then((res) => {
setIsLoading(true);
if (res) {
redirectByUserType(res.data.type); // redirect to /home
}
});
}, []);
I tried using useLayoutEffect but not working.
Please help me, thank you so much.
If you don't want to render until getProfile() has finished, then have a state variable which tracks whether it is finished. If it hasn't finished, return null to render nothing. If it has, return whatever you want to render. I would normally call this state variable loading, but you seem to already have one with that name, who's purpose i don't know. Maybe you can piggy back on that, maybe you need a separate one:
const [ready, setReady] = useState(false);
useEffect(() => {
getProfile().then(res => {
setIsLoading(true);
if(res) {
redirectByUserType(res.data.type);
} else {
setReady(true)
}
});
}, []);
if (!ready) {
return null;
}
return (
<div>Something</div>
);

reactjs and firebase: not redirecting to admin-dashboard and need to click twice before login

My admin has a custom claim property:
admin: true
I'm checking when logging in:
async function handleLogin(e){
e.preventDefault();
try{
await login(email, password);
currentUser.getIdTokenResult().then(idTokenResult =>{
if(idTokenResult.claims.admin){
console.log("is admin: "+idTokenResult.claims.admin);
setIsAdmin(true);
}
})
if(isAdmin){
history.push("/admin/dashboard");
}else{
if(currentUser.emailVerified == false){
emailNotVerified();
}
else{
history.push("/user/dashboard");
}
}
} catch{
wrongEmailOrPass();
}
}
But I'm having 2 issues:
every type of user is getting redirected to /user/dashboard
I need to click twice before logging in. The first time I click it'll give me incorrect email/password and the next time it'll redirect me to the dashboard page
Although I'm not sure is this some kind of state issue?
One think I can notice in your code is that you don't handle this async call properly and it wont behave as you expect it to. Try to change this:
currentUser.getIdTokenResult().then(idTokenResult =>{
if(idTokenResult.claims.admin){
console.log("is admin: "+idTokenResult.claims.admin);
setIsAdmin(true);
}
})
to this:
const idTokenResult = await currentUser.getIdTokenResult();
if (idTokenResult.claims.admin) {
console.log("is admin: " + idTokenResult.claims.admin);
setIsAdmin(true);
}
And the second part is as you asumed the state. You set the state isAdmin and continue on with your synchonous code. You can set that satate but store it also in a variable for your code that comes after it.
I would also recommend to use the onAuthStateChanged listener to detect auth state changes:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
You can read more about it here.

Function inside useEffect fire twice even with empty dependency array

I have this example from https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
The effect works fine (fires once) but for some reason, the functions inside are called twice.
useEffect(() => {
const cancelAuthListener = firebase
.auth()
.onIdTokenChanged(async (user) => {
console.log('once or twice?')
if (user) {
// This fires twice
const userData = await mapUserData(user)
setUserCookie(userData)
setUser(userData)
} else {
removeUserCookie()
setUser()
}
})
const userFromCookie = getUserFromCookie()
if (!userFromCookie) {
router.push('/')
return
}
setUser(userFromCookie)
console.log(' i fire once')
return () => {
console.log('clean up')
cancelAuthListener()
}
}, [])
How can I make it to fire once?
I added some console logs:
On the first render I get: 'i fire once', 'once or twice', 'once or twice'
If I leave the page the cleanup console log fires (as it's supposed to do)
Many thanks
Later edit:
this is the code
export const mapUserData = async (user) => {
const { uid, email } = user
const token = await user.getIdToken()
return {
id: uid,
email,
token
}
}
If getIdToken() gets 'true' as an argument it will force a refresh regardless of token expiration.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken
Solved!!
the user was calling getIdToken(true) which forces a refresh.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken
Sorry guys, my bad!!!
You have a setState() inside useEffect thats the culprit, where useEffect having empty params [], one request on initial mount and another when do
setUser(userData) the component re-renders and useEffect() is invoked again.
Instead of using user as state, try using as ref and check. That might resolve this.

Resources