In my react app, I am trying to accept the user information and build some local objects and then navigate to some url.
const mapDispatchToProps = (dispatch) => {
return {
handleLogin: (userName, password) => {
dispatch(login(userName, password)).then(
(response) => {
if(!response.error){
dispatch(buildProfile(response.payload.data));
browserHistory.push('/'); // <-- This does not work in the first call to handleLogin and always works in the second call
}
else{
dispatch(loginFailed(response.payload));
}
});
}
};
};
The line browserHistory.push('/'); never gets called first time I click login button, though the buildProfile returns successfully. Only when I click login next time, redirection happens. What could be going wrong? And this happens consistently.
Thanks in advance.
There was a null variable access in my JSX (not posted here) which was crashing in response to the state update after first dispatch. After fixing that, this works as desired.
Related
I'm trying to implement a redirect using push() from useRouter and getting confusing behavior. My use case is simple: after a user signs in, I want to call router.push('/') to send them to the home page. However, calling router.push('/') after completing the signin flow does nothing. Here's the signin function. It's worth noting that this function gets called from a child component.
const authHandler = useCallback(async (emailCode) => {
setIsLoading(() => true);
try {
const result = await signIn.attemptFirstFactor({
strategy: 'email_code',
code: emailCode,
});
createAlerts('Success', false);
router.push('/') // this does nothing;
} catch (error) {
handleForeignAlert(error, 'Code is incorrect or expired');
} finally {
setIsLoading(() => false);
}
});
Why doesn't calling push() here work? In all the examples in the NextJS docs, it seems like calling it from within a function initiated by a click is exactly what I should do.
The only times router.push('/') does work is from within a useEffect() call after I manually reload the page, like this:
useEffect(() => {
if (userId) {
router.push('/');
}
}, [userId]);
Also worth noting is that other router methods, like reload(), work fine in the same place.
What rule or behavior am I missing here?
I have the following code in my React project using Supabase:
// supabaseClient.ts
export const onAuthStateChangedListener = (callback) => {
supabase.auth.onAuthStateChange(callback);
};
// inside user context
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((event, session) => {
console.log(event);
});
return unsubscribe;
}, []);
However, every time I switch tabs away from the tab rendering the website to something else, and back, I see a new log from this listener, even if literally no change happened on the website.
Does anyone know the reason for this? The useEffect inside my user context component is the only place in my app where the listener is being called. To test, I wrote this dummy function inside my supabaseClient.ts file:
const testFunction = async () => {
supabase.auth.onAuthStateChange(() => {
console.log("auth state has changed");
});
};
testFunction()
This function also renders every time I switch tabs. This makes it a little annoying because my components that are related to userContext re render every time a tab is switched, so if a user is trying to update their profile data or something, they cannot switch tabs away in the middle of editing their data.
Supabase onAuthStateChange by default triggers every time a tab is switched. To prevent this, when initializing the client, add {multiTab: false} as a parameter.
Example:
const supabase = createClient(supabaseUrl, supabaseAnonKey, {multiTab: false,});
Here is my solution to the same problem. The way I've found is saving the access token value in a cookie every time the session changes, and retrieve it when onAuthStateChange get triggered, so I can decide to not update anything if the session access token is the same.
// >> Subscribe to auth state changes
useEffect(() => {
let subscription: Subscription
async function run() {
subscription = Supabase.auth.onAuthStateChange(async (event, newSession) => {
// get current token from manually saved cookie every time session changes
const currentAccessToken = await getCurrentAccessToken()
if (currentAccessToken != newSession?.access_token) {
console.log('<<< SUPABASE SESSION CHANGED >>>')
authStateChanged(event, newSession)
} else {
console.log('<<< SUPABASE SESSION NOT CHANGED >>>')
}
}).data.subscription
// ** Get the user's session on load
await me()
}
run()
return function cleanup() {
// will be called when the component unmounts
if (subscription) subscription.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Edit - Fixed The problem was absolute garbage redux code as it was my first time using it. I studied Redux and rewrote my code and it works fine. It wasn't working because there was way too much garbage code working at once.
I'm using nextJS and when visiting a shared URL such as /username/p/postID I want to display the initial post modal/popover.
async function presentInitialPost(postID: string) {
const postSnap = await db.collection("posts").doc(postID).get();
const postData = postSnap.data();
if(postData){
dispatch(showModalPostView(postData as Post));
}
}
useEffect(() => {
if(initialPostID){
presentInitialPost(initialPostID)
}
}, [initialPostID])
Errors (there ae numerous of each):
"You may not unsubscribe from a store listener while the reducer is executing."
"You may not call store.getState() while the reducer is executing."
I use the same dispatch throughout my app just fine - and if I call presentInitialPost on a button click instead - it works completely fine.
I've tried delays and debugging where the error is coming from but I haven't figured it out at all, any help is appreciated, thank you.
Redux code:
showModalPostView(state, action) {
state.showModalPostViewFunction(action.payload);
},
setShowModalPostViewFunction(state, action) {
state.showModalPostViewFunction = action.payload;
return state;
},
showModalPostViewFunction: (post: Post) => {},
showModalPostViewFunction comes from my overlay wrapper component
showModalPostView = (post: Post) => {
console.log(this.state)
this.setState({
showModalPostView: true,
modalPostViewPost: post,
});
};
When the modal post view is shown here, it contains multiple useSelectors which cause the errors - but only when i present the modal post view in useEffect - it works just fine throughout the app when dispatched through a click action.
In modal post view various selectors throw the errors:
const likedPosts = useSelector((state: ReduxRootState) => state.likedPosts);
It appears you are not using redux the way it was intended to be used. What you pass to dispatch() is supposed to be an action. A reducer receives that action and returns new state, and then then new state is sent to your subscriber (the React component).
You are instead calling a function showModalPostView which is calling some setState function in the parent component and is returning who knows what. That return value is being passed as an argument dispatch, which kicks off a reducer. However, that setState is likely causing your child component to unsubscribe from the store so it can re-render.
It like you aren't actually dispatching an action; you're just calling a function, so you shouldn't be using dispatch at all.
async function presentInitialPost(postID: string) {
const postSnap = await db.collection("posts").doc(postID).get();
const postData = postSnap.data();
if(postData){
showModalPostView(postData as Post);
}
}
Every time I reload the my account page, it will go to the log in page for a while and will directed to the Logged in Homepage. How can I stay on the same even after refreshing the page?
I'm just practicing reactjs and I think this is the code that's causing this redirecting to log-in then to home
//if the currentUser is signed in in the application
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
unsubscribe();
resolve(userAuth); //this tell us if the user is signed in with the application or not
}, reject);
})
};
.....
import {useEffect} from 'react';
import { useSelector } from 'react-redux';
const mapState = ({ user }) => ({
currentUser: user.currentUser
});
//custom hook
const useAuth = props => {
//get that value, if the current user is null, meaning the user is not logged in
// if they want to access the page, they need to be redirected in a way to log in
const { currentUser } = useSelector(mapState);
useEffect(() => {
//checks if the current user is null
if(!currentUser){
//redirect the user to the log in page
//we have access to history because of withRoute in withAuth.js
props.history.push('/login');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[currentUser]); //whenever currentUser changes, it will run this code
return currentUser;
};
export default useAuth;
You can make use of local storage as previously mentioned in the comments:
When user logs in
localStorage.setItem('currentUserLogged', true);
And before if(!currentUser)
var currentUser = localStorage.getItem('currentUserLogged');
Please have a look into the following example
Otherwise I recommend you to take a look into Redux Subscribers where you can persist states like so:
store.subscribe(() => {
// store state
})
There are two ways through which you can authenticate your application by using local storage.
The first one is :
set a token value in local storage at the time of logging into your application
localStorage.setItem("auth_token", "any random generated token or any value");
you can use the componentDidMount() method. This method runs on the mounting of any component. you can check here if the value stored in local storage is present or not if it is present it means the user is logged in and can access your page and if not you can redirect the user to the login page.
componentDidMount = () => { if(!localStorage.getItem("auth_token")){ // redirect to login page } }
The second option to authenticate your application by making guards. You can create auth-guards and integrate those guards on your routes. Those guards will check the requirement before rendering each route. It will make your code clean and you do not need to put auth check on every component like the first option.
There are many other ways too for eg. if you are using redux you can use Persist storage or redux store for storing the value but more secure and easy to use.
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.