Vue.js - Helper function that accesses the app instance - javascript

Let's say I want to make a utility function that redirects users to a route if they aren't authenticated.
This function resides in a utils.js file; it isn't declared in any one component.
// utils.js
export function redirectIfNotAuthenticated (app, path) {
if (!app.$store.state.isAuthenticated) {
app.$router.push(path)
}
}
this function is then imported into single-file components and called like:
redirectIfNotAuthenticated(this, '/login').
I think having an app parameter and having to explicitly pass this from the component is ugly. Is there a better way to have a stateless helper function access the scope of the calling component in vue? As in, is there a way to bind functions that don't live inside of a .vue file to the calling instance of the vue app?

To handle redirection, instead of putting logic inside component, you can use vue-router's navigation guards to handle on top level instead:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
if (!isAuthenticationRequired(to)) { // your logic to ignore authentication for custom routes, e.g. login
next();
} else if (!isAuthenticated()) { // your logic which needs access to Vuex's store
next('login');
} else {
next();
}
})
Then, if you create a store object and add it to your Vue app instance, like in the default example of Vuex , you can refer to the object directly without any need to access the Vue app, since same store object is used:
// utils.js
import store from '../your_path/your_file.js';
export function isAuthenticated() {
return store.state.isAuthenticated;
}

The best practice in this situation is to use router navigation guards :
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
next(vm=>!vm.$store.state.isAuthenticated)
//vm refers to the vue instance
})

Related

VueJS lifecycle order when combined with router

I need to run an async initialize method when my app starts. so I did it inside beforeCreate of App.vue as below.
app.vue:
export default {
...
beforeCreate () {
console.log('before create app.vue')
}
}
Also, I need to check a condition before entering any route, so I did it inside beforeEach of router.
main.js:
router.beforeEach((to, from, next) => {
if (condition) {
console.log('router before each')
}
})
The problem is I need to check the condition after my initialization is done, but, when I launch my app, the condition is checked before the initialization and my output is:
router before each
before create app.vue
What am I missing? and how can I fix this?
here is how I solved it in vue3 but you can use a similar approach... basically don't mount the app until the initialization is completed. In my case the authCheck in the initialization function you mention full source
import useFirebaseAuth from "./hooks/firebase-auth";
const { authCheck } = useFirebaseAuth();
const app = createApp(App).use(IonicVue);
authCheck()
.then(() => {
app.use(router);
return router.isReady();
})
.then(() => {
app.mount("#app");
});
Yap that is how it suppose to be, the navigation guard is always triggered whenever there is a navigation, meaning it gets executed once it receives your navigation, which is before your component gets created. You can execute anything within your main.js file i don't fully understand what you want to achieve but you can utilize any of the Vue Js lifecycle hooks(https://v2.vuejs.org/v2/guide/instance.html) to get what you want or the navigation guards(https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards) beforeRouteEnter, beforeRouteLeave and beforeRouteUpdate
They all execute at different times, hope this helps

Access React context from regular function

Is it possible to access React context from a non-React helper function?
For example, I have a context provider that saves the root slug for a case study page when the app loads. I then need to build URLs, consuming the context provider at various places around my app. So I've created a helper function to do so:
components/caseStudies/index.jsx
import getCaseStudyPath from '../../lib/helpers/getCaseStudyPath';
const CaseStudies = ({ caseStudy }) => {
// caseStudy.slug === 'test-case-study'
const caseStudyPath = getCaseStudyPath(caseStudy.slug);
return (
<a href={caseStudyPath}>Test</a>
);
};
lib/helpers/getCaseStudySlug
export default (slug) => {
// Get caseStudyRootSlug from React context
return `/${caseStudyRootSlug}/${slug}`;
// For example, this returns '/case-studies/test-case-study'
};
I know I can just consumer the context provider in my React component, or even as a HOC, and just pass the value to the helper function, but I need to use this helper function throughout my app and don't want the overhead of setting up a consumer every time I want to use it.

How to get route parameter in aurelia custom component

I'm creating a custom component in aureliajs framework and injecting it a Router instance:
#inject(Router)
export class Pagination {
constructor(router) {
this.router = router;
}
}
It will be used with list view-models in order to setup some basic pagination. Therefore I need to read current page number from an active route (that would look like: orders/:pageNum. I'm not sure however how to do it? I mean - I know it should probably be placed in Pagination attached method, but how to get to this :pageNum param?
Create a bindable property in your custom element. Take the page number from the active route and bind it to the custom element. For instance:
import { bindable } from 'aurelia-framework';
#inject(Router)
export class Pagination {
#bindable page;
constructor(router) {
this.router = router;
}
}
Usage:
<pagination page.bind="page"></pagination>
To get the page number in the active route, use the activate() hook:
activate(params) {
this.page = params.pageNum;
}
You can use router.currentInstruction.params and router.currentInstruction.queryParams.
You can also observe the router to be notified when the route changes:
let sub = this.bindingEngine.propertyObserver(
this.router, 'currentInstruction').subscribe(currentInstruction => {
console.log(currentInstruction);
});

How to call dispatch from onChange in react-router Route?

I am developing a web application where I want to use the URL query for running search queries.
When executing a search, a new request should be sent to the server to get new data, according to the url query params.
Using react-router and Redux, I want to call the dispatch function to fetch the new data from the server according to the query (i.e url change).
I was thinking to use the onChange event and call the dispatch function, like this:
<IndexRoute component={Catalog} onChange={(prevState, nextState, replace) => {dispatch(fetchProducts(nextState.location.search))}}/>
But since my routes is not a React component, I cant access the dispatch function.
I can import my store and use store.dispatch, but I read it is not recommended.
What would you suggest me to do?
To solve this kind of issues, we always declare or routes in a function similar to this:
function createRoutes(store) {
return {
component: App,
childRoutes: [
// Here are defined the other routes.
// They can be defined using plain objects:
{path: "profile", component: Profile},
// Or generated through a function call:
...require("./some/module")(store),
]
}
}
When you do this it is fairly simple to use the store to dispatch an action or to do calculations in React Router lifecycle hooks.
function createRoutes(store) {
return {
component: Settings,
onEnter: (nextState, replace, callback) => {
const state = store.getState()
if (!state.user.logged) {
dispatch(createNotification("You must be logged to perform this"))
replace("/login")
}
callback()
}
}
}

Where does one hold service instances in a react/redux application?

Suppose I am writing an application in Redux and I am tasked to add logging using a 3rd party library. Its API is as follows:
function createLogger(token) {
// the logger has internal state!
let logCount = 0;
return {
log(payload) {
logCount++; // modify local state
fetch('/someapi', { // ship payload to some API
method: 'POST',
body: payload
});
}
};
}
I would then use the library something like this:
let logger = createLogger('xyz');
logger.log('foobar');
I definitely want to create the logger instance just once during application init. But then the question is: where do I store the logger instance?
First instict is to put it somewhere in the store. But is that a good idea? As I have demonstrated in the code the logger object is stateful, it stores a counter in the closure. I do not get a new instance like I would with an immutable object. As we know, state should only be modified via pure reducer functions.
Other possibilities are to create the instance somewhere in a redux middleware closure or just create a global variable, which is obviously evil in terms of testability.
Is there a best practice for this (I would think) rather common scenario?
Since you are using ES6 modules I would setup your logger as a module, export it, and import it wherever you plan to use it. I think logging from the actions is a solid plan, since it keeps the components unaware, and doesn't pollute the store with side-effects.
function createLogger(token) {
// the logger has internal state!
let logCount = 0;
return {
log(payload) {
logCount++; // modify local state
fetch('/someapi', { // ship payload to some API
method: 'POST',
body: payload
});
}
};
}
export default const logger = createLogger('xyz');
Your action creators
import logger from 'logger-module';
//
logger.log('somestuff');
Testing is still easily achievable by importing the logger and placing whatever spy/stub on its methods that you need to intercept.
From the Redux documentation:
/**
* Sends crash reports as state is updated and listeners are notified.
*/
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
Raven being a third-party library.
If the library has its own state then it shouldn't be an issue using it in middleware (the state belongs in the library and not your app). If you're creating a state for it, for some reason, then that state should belong in the Redux store, probably under store.logger or something.

Categories

Resources