React Router check condition before route change - javascript

Is it possible to check a condition before changing routes?
I have this function which listens to routes and logs everytime the app changes. I use it for debugging purposes but is it possible to check something like if localstorage contains a value, if not move to login page?
useEffect(() => {
history.listen(loc => {
console.log(`route: ${loc.pathname}`);
});
}, []);
Something like if (loc.pathname !== '/login') push to login. Within this history.listen function
I've tried using protected routes but I am getting an error where the graphql client loads the user in but if the token expires, it still loads the component. Then redirects.

Yes, you can do it using history.push method:
useEffect(() => {
history.listen((loc) => {
if (localStorage.getItem('someItem')) {
history.push('/login');
}
});
}, []);
Read about that method and other available methods you can read in documentation.

To change the current route, you can use the following line:
window.location = '<YOUR_ROUTE>';
So for example, if you configured your login route to be like:
<Route path="/login" component={Login} />
Then, in your useEffect function, you would just add the line
useEffect(() => {
history.listen(loc => {
console.log(`route: ${loc.pathname}`);
window.location = '/#/login'; // <-- this line here
});
}, []);
And if you need to check a condition first before redirecting to the login route, you'll juste need to put the window.location line within your if, like:
if (loc.pathname !== '/login') {
window.location = '/#/login';
}

Related

Link component interpolation error nextjs

I am getting this error in Next.js:
Error: The provided 'href' (/subject/[subject]) value is missing query values (subject) to be interpolated properly. Read more: https://err.sh/vercel/next.js/href-interpolation-failed`.
I have a dynamic page set up as /subject/[subject].tsx. Now in my navigation I have:
<Link href={'/subject/${subject}'} passHref><a>{subject}</a></Link>
It works fine when I access the page with different paths but when I am pressing on a button on the page it throws the error above which I imagine is because the component rerenders. If you go to the page in the error it says: Note: this error will only show when the next/link component is clicked not when only rendered.
I have tried to look for a solution and I tried doing:
<Link href={{pathname: '/subject/[subject]', query: {subject: subject}}}></Link>
but nothing changed. I read the docs and it seems that the as prop is only an optional decorator that is not used anymore so I fail to see how that can help me.
I got the same issue when trying to redirect user to locale. I did it in useEffect. After investigate I discovered that on first render router.query is empty, so it's missing required field id. I fix it by using router.isReady
export const useUserLanguageRoute = () => {
const router = useRouter()
useEffect(() => {
const {
locales = [],
locale,
asPath,
defaultLocale,
pathname,
query,
isReady // here it is
} = router
const browserLanguage = window.navigator.language.slice(0, 2)
const shouldChangeLocale =
isReady && // and here I use it
locale !== browserLanguage
&& locale === defaultLocale
&& locales.includes(browserLanguage)
if (shouldChangeLocale) {
router.push(
{
pathname,
query,
},
asPath,
{ locale: browserLanguage }
)
}
}, [router])
}
Another possible solution could be redirect using the router.push function:
const myRedirectFunction = function () {
if (typeof window !== 'undefined') {
router.push({
pathname: router.pathname,
query: {...router.query, myqueryparam: 'myvalue'},
})
}
}
return (
<>
<button onClick={() => {myRedirectFunction()}}> Continue </button>
</>
)
It is important to include ...router.query because there is where the [dynamic] current value is included, so we need to keep it.
Reference: https://github.com/vercel/next.js/blob/master/errors/href-interpolation-failed.md
You better do a null || undefined check in prior.
#Bentasy, Like you rightly pointed out, any re-render of the page is expecting you to pass in the dynamic route params again. The way I avoided page re-rendering with subsequent clicks on the page was to replace Link tag on the page with a label tag. I was using the Next Link tag to navigate between the tabs on this image which generated this same error. So I replaced the Link tags with label tags and the error was solved.
I was having the same problem, this is how I solved it.
While pushing something to the router, we need to give the old values ​​in it.
In that:
const router = useRouter()
router.push({
...router,
pathname: '/your-pathname'
})
try add this function, it work for me:
export async function getServerSideProps(context) {
return {
props: {},
};
}

Cypress before hook repeats after each it statement

I am new to cypress when go to a url using a before hook it completes the first it statement, then it executes the before hook again and does the 2nd it statement, it should execute the before hook once execute the it statements and stop. However that is not what is happening and i need help please to see what i am doing wrong. My code is below:
import { expect, assert, should } from 'chai';
import LoginPage from '../page-objects/LoginPage.spec'
import BeHomePage from '../page-objects/BeHomePage.spec'
describe('create, edit, delete Survey', () => {
before(function() {
cy.visit(''); // see config json for base url
})
it('user can login to boardeffect', () => {
LoginPage.getLoginUserName().type(Cypress.env('username'));
LoginPage.getLoginUserPassword().type(Cypress.env('password'));
LoginPage.getSigninButton().click();
})
it('user can navigate to collaborate', () => {
BeHomePage.getAutoBeWorkroom().should('be.visible');
BeHomePage.getAutoBeWorkroom().click();
BeHomePage.getCollaborate().should('be.visible');
BeHomePage.getCollaborate().click();
cy.wait(10000)
})
})
{
"baseUrl": "https://automation-ozzie.boardeffect.com/login",
"defaultCommandTimeout": 15000,
"env": {
"username": "steveSystem",
"password": "Passw0rd"
}
}
class BeHomePage {
getAutoBeWorkroom() {
return cy.get(':nth-child(7) > a')
}
getCollaborate(){
return cy.get('.workroom-buttons > :nth-child(4) > a')
}
getNewSurvey(){
return cy.get('btn btn-success')
}
}
export default new BeHomePage();
As picture below shows all code in one (it) block passes as seen in picture however code in 2 it blocks fails as seen above.
enter image description here
The extra step you need is discussed in this section of the docs Cookies - Preserve Once.
Cypress auto-clears cookies between each it(), but if you add
it('user can login to boardeffect', () => {
LoginPage.getLoginUserName().type(Cypress.env('username'));
LoginPage.getLoginUserPassword().type(Cypress.env('password'));
LoginPage.getSigninButton().click();
// preserve the cookie set above for the next test
Cypress.Cookies.preserveOnce('_boardeffect_session');
})
it will keep that cookie value during subsequent it() sections.
Open the browser dev-tools, go to Application, Cookies, https://automation-ozzie.boardeffect.com and observe that without preserveOnce() the cookie value is cleared.

firebase.auth().onAuthStateChanged getting null after refresh page in vuex

i am using Vuex and Firebase Authentication. I got stuck when reload the page. firebase.auth().onAuthStateChanged take time to response. But i need at the same time when reload the page. I have seen many tutorials in internet, most of them is router guard, but that i don’t want. I have some route where the user has login, then can navigate to this route.
App.vue where i am applying.
created () {
firebase.auth().onAuthStateChanged(async user =>{
if (user){
await this.$store.dispatch('autoSignIn',user)
}
})
}
Here is my vuex action theat trigger when page reload to auto sign in if a user was logged before reload the page.
autoSignIn ({commit}, payload) {
commit('setUser',{email:payload.email, userId:payload.uid})
}
This is my getter
isAuthenticated:state => {
return state.user !== null && state.user !== undefined ? state.user : null
}
Here is where i am calling my getter isAuthenticated.
getEventsByUser({getters,commit}){
let data = [];
firebase.database().ref('usuario/' + getters.isAuthenticated.userId + '/eventos/')
.on("value", eventos =>{
eventos.forEach(evento =>{
data.push({"id":evento.key, ...evento.val()})
});
commit('setEventsByUser',data)
})
},
And this is the component which dispatch the action
<template>
<div>
<div v-for="(event,id) in getEventsByUser" :key="id">
{{event}}
</div>
</div>
</template>
<script>
export default {
name: "MyEvents",
computed:{
getEventsByUser(){
return this.$store.getters.getEventsByUser;
},
},
mounted() {
this.$store.dispatch('getEventsByUser')
},
}
Here is the error when i reload the page
When the page loads, Firebase checks whether the ID token that is stored for the user is still valid. This requires that it calls the server, so it may take a moment. During this check the user will be null, so your code needs to handle that everywhere.
In your onAuthStateChanged handler you handle this correctly with:
firebase.auth().onAuthStateChanged(async user =>{
if (user){
But then in getEventsByUser you assume there is a user, which (as shown by the error message) is not true. So you'll want to add a check there, to see if there's a user, before attaching the listener to the database:
getEventsByUser({getters,commit}){
let data = [];
if (getters.isAuthenticated) {
firebase.database().ref('usuario/' + getters.isAuthenticated.userId + '/eventos/')
...

Vue-Router Data Fetch: Fetch Data before 'beforeRouteUpdate' loads component

I am new to vue-router navigation guards and so I recently realized that I needed to use beforeRouteUpdate guard for reused components where for example: Going from /foo/1 to /foo/2
However, while coming to /foo/1, I pulled data from database through an axios call and before going to /foo/2, I need to pull new data again through the axios call.
This is where I face a problem where the navigation guard beforeRouteUpdate loads the component /foo/2 before my data loads from the axios call and thus I get null in a few of my variables.
How can I make beforeRouteUpdate wait to load the next component so that all my data is loaded from the axios calls?
As for my code, it looks like this:
beforeRouteUpdate (to, from, next) {
Vue.set(this.$store.state.user, 'get_user', null)
this.$store.dispatch(OTHER_PROFILE_GET, to.params.id).then(resp => {
console.log(resp);
if(this.$store.getters.is_user_loaded) {
next()
} else {
this.$store.watch((state, getters) => getters.is_user_loaded, () =>
{
if(this.$store.getters.is_user_loaded) {
console.log(this.$store.state.user.get_user);
console.log('comes here');
next()
}
})
}
})
},
To explain my code further, I have called this method in my component and so I when I go from /user/1 to /user/2 I dispatch a Vuex action which makes an axios call to get the new profile details but before the axios call completes and loads the data in the Vuex state, the beforeRouteUpdate already loads the next component.
First, your action should perform any state mutation such as setting user.get_user to null. I'm also not sure why you've added a watch; your action should only resolve when complete. For example
actions: {
[OTHER_PROFILE_GET] ({ commit }, id) {
commit('clearUserGetUser') // sets state.user.get_user to null or something
return axios.get(`/some/other/profile/${encodeURIComponent(id)}`).then(res => {
commit('setSomeStateData', res.data) // mutate whatever needs to be set
})
}
}
then your route guard should have something like
beforeRouteUpdate (to, from, next) {
this.$store.dispatch(OTHER_PROFILE_GET, to.params.id).then(next)
}
In order to prevent errors from trying to render null data, use your getters. For example, say your getter is
getters: {
is_user_loaded (state) {
return !!state.user.get_user
}
}
in your component, you can map this to a computed property...
computed: {
isUserLoaded () {
return this.$store.getters.is_user_loaded // or use the mapGetters helper
},
user () {
return this.$store.state.user.get_user // or use the mapState helper
}
}
then in your template, use this logic to conditionally render some data
<div v-if="isUserLoaded">
Hello {{user}}
</div>
<div v-else>
Loading...
</div>
This is the suggested approach in the vue-router guide for beforeRouteUpdate

Navigo JS Router - Duplicate routing issue

Not sure what the issue is but my Navigo router is duplicating routes.
The Router:
this.Navigo.hooks({
before: (done, params) => {
// some tomfoolery
done();
}
});
this.Navigo.on({
'/:region/travel': (params) => {
// import Travel module
// some nonsense
},
'/:region/travel/car': (params) => {
// import TravelCar module
// some nonsense
}
)};
this.Navigo.resolve();
The Problem
this.Navigo.navigate('/london/travel/car');
Navigating to /london/travel/car is also triggering the route for /london/travel and thus causing all kinds of twaddle.
Is this standard behaviour? If not, what could be wrong?
I could rewrite the routes so they don't collide e.g. /london/travel-by-car, but I really don't want to if I can avoid it.
UPDATE 1:
I tried switching the order of routes but makes no difference. I did this by declaring the longest travel routes first, /:region/travel/car, and the smallest, /:region/travel, last.
UPDATE 2:
The more I look into this, the more I'm convinced this cannot be achieved with Navigo. Navigo do not support nested routes. If somebody could confirm that my routes are in fact 'nested', I will use an alternative routing library that does support them.
My code is a little different, but works the way you expect:
var router = new Navigo("/");
var render = (content) => (document.querySelector("#app").innerHTML = content);
router
.on('/:id', ({ data }) => {
setuserId(data.id)
if (verifiedUser) {
console.log("User verified");
} else {
console.log("User NOT verified");
}
rendertemplate(userDataURL(), "#landing-template", "#app")
})
.on('/:id/q', ({ data }) => {
// Example - flaging a send with 's' from 'SMS', perhaps a diff flow?
setuserId(data.id)
rendertemplate(userDataURL(), "#landing-template", "#app")
console.log("Source was a QRcode");
})
.on('/:id/q/t', ({ data }) => {
// Example - flaging a send with 's' from 'SMS', perhaps a diff flow?
setuserId(data.id)
rendertemplate(userDataURL(), "#landing-template", "#app")
console.log("Source was a QRcode in a Train");
})
This will give me a single discreet ".. verified"/"Source was a QRcode"/"Source was a QRcode in a Train" console.log response.
B

Categories

Resources