I've recent switched over to Quasar v2 and I'm having trouble getting the info I need for the vue apollo setup. I am using https://apollo.vuejs.org/guide/installation.html#_1-apollo-client to try and install vue apollo into the framework using the boot file.
This is my boot/apollo.ts file
import { boot } from 'quasar/wrappers';
import ApolloClient from 'apollo-boost';
import VueApollo from 'vue-apollo';
const apollo = new ApolloClient({
uri: 'https://api.graphcms.com/simple/v1/awesomeTalksClone',
});
const apolloProvider = new VueApollo({
defaultClient: apollo,
});
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$apollo
app.config.globalProperties.$apollo = apollo;
// ^ ^ ^ this will allow you to use this.$apollo (for Vue Options API form)
// so you won't necessarily have to import apollo in each vue file
});
export { apollo, VueApollo, apolloProvider };
And this is where I am trying to use it:
import { Vue } from 'vue-class-component';
export default class LoginPage extends Vue {
public login() {
console.log(this.$apollo);
}
}
The error I'm getting is
Property '$apollo' does not exist on type 'LoginPage'.
I can see in the comment for the globalProperties it mentions the vue options api. Not sure if this is happening because I use vue-class-component.
I ended up added this below the export
declare module '#vue/runtime-core' {
interface ComponentCustomProperties {
$apollo: FunctionConstructor;
}
}
Related
I encounter a strange behavior with pinia in a Vue3 app.
I created a little app with a pinia store using option API.
Here is my main.js with creating the store :
import { createApp } from "vue";
import { createPinia } from "pinia";
// Vue Router
import index from "./router";
// import { useAspergesStore } from "./store/storeAsperges";
import App from "~/App.vue";
import "~/styles/tailwind.css";
import "~/styles/main.scss";
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(index);
app.mount("#app");
Here is my store :
import { defineStore } from 'pinia'
import axios from "axios";
export const useAspergesStore = defineStore('asperges', {
state: () => ({
listeCueilleurs: JSON.parse(localStorage.getItem("listeCueilleurs")) || [],
}),
getters: {
...
},
actions: {
...
},
})
And I call the store from my components :
import { useAspergesStore } from '../../../store/storeAsperges.js';
import { mapStores } from 'pinia';
...
computed: {
...mapStores(useAspergesStore),
},
When I start the web page, I can't get the datas from the store, even on a reload. The store is not loaded.
When I open the devTools in chrome, it doesn't show that the store is loaded.
When I click on the vueDevTools, the store loads and the datas appear in the web page.
I get the message in the console :
"🍍 "asperges" store installed 🆕"
It's like starting the vueDevTools triggers the store. And all work fine after that.
If you have any idea of what I'm doing wrong, any help would be appreciated.
Ok I found a solution. I don't know if it's the right one, but it works.
I just tried to call the store from the component in the mounted() hook and now the store loads correctly.
But anyway, I don't know why the store didn't load even if the datas were used in the components...
I am trying to implement Google OAuth2 using 'vue3-google-oauth'.
I tried the method below, but the signIn() function returned false.
Please tell me how to use 'vue3-google-oauth' with boot files in Quasar.
this is my code.
googleoauth.js
import { boot } from 'quasar/wrappers';
import GAuth from 'vue3-google-oauth2';
let app;
export default boot(({ app }) => {
const gAuthOption = {
clientId: '835170725866-nt6qibip4tb7jafav2k20j83ipso67mo.apps.googleusercontent.com',
scope: 'profile email',
prompt: 'select_account',
};
app.use(GAuth, gAuthOption);
app = app;
});
export { app };
quasar.config.js
...
boot: ['googleoauth'],
...
LoginPage.vue ----- I want to use Google OAuth2 in this file
import { getCurrentInstance } from 'vue';
const app = getCurrentInstance();
app.appContext.config.globalProperties.$gAuth.signIn(); // return false (not working)
The problem: i have many sagas that do not handle an 401 error in response status, and now i have to deal with it. I have apiservice based on apisause and i can write an response monitor with it to handle 401 error (like interceptors in axios). But i cant dispatch any action to store to reset user data, for example, because there is no store context in apiservice. How to use dispatch function in apiservice layer? Or use put() function in every saga when i recieve 401 response status is the only right way?
you can use refs for using navigation in 'apisauce' interceptors
this is my code and it works for me ;)
-- packages versions
#react-navigation/native: ^6.0.6
#react-navigation/native-stack: ^6.2.5
apisauce: ^2.1.1
react: 17.0.2
react-native: ^0.66.3
I have a main file for create apisauce
// file _api.js :
export const baseURL = 'APP_BASE_URL';
import { create } from 'apisauce'
import { setAPIInterceptors } from './interceptors';
const APIClient = create({ baseURL: baseURL })
setAPIInterceptors(APIClient)
and is file interceptors.js I'm watching on responses and manage them:
// file interceptors.js
import { logout } from "../redux/actions";
import { store } from '../redux/store';
import AsyncStorage from '#react-native-async-storage/async-storage';
export const setAPIInterceptors = (APIClient) => {
APIClient.addMonitor(monitor => {
// ...
// error Unauthorized
if(monitor.status === 401) {
store.dispatch(logout())
AsyncStorage.clear().then((res) => {
RootNavigation.navigate('login');
})
}
})
}
then I create another file and named to 'RootNavigation.js' and create a ref from react-native-navigation:
// file RootNavigation.js
import { createNavigationContainerRef } from '#react-navigation/native';
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
if (navigationRef.isReady()) {
navigationRef.replace(name, params);
}
}
// add other navigation functions that you need and export them
then you should to set some changes in you App.js file:
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
finally in anywhere you can call this function for use react native navigations
full focument is in here that explain how to Navigating without the navigation prop
Navigating without the navigation prop
How to access boot file configured axios from Quasar V2 Composition API setup function, without importing axios in every file?
Since this.$axios can be used only for Options API, I tried to access through the context parameter of setup function.
Even though it works, context.root is now deprecated in Vue 3.
I do not want to import axios in every file as shown in the example at https://next.quasar.dev/quasar-cli/ajax-requests
For setup method access, I think it is still not implemented since mentioned as a TODO activity at https://next.quasar.dev/quasar-cli/boot-files#examples-of-appropriate-usage-of-boot-files
Similar to axios, usage of vue-i18n also from boot file is an issue for me.
setup (props, context) {
context.root.$axios({
method: 'get',
url: 'http://localhost:8080/storemgr/item/3',
}).then((response: any) => {
console.log(response)
}).catch((error: any) => {
console.log(error)
})
...
}
Below is my axios boot file contents generated by Quasar V2 CLI
import axios, { AxiosInstance } from 'axios'
import { boot } from 'quasar/wrappers'
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance;
}
}
export default boot(({ Vue }) => {
// eslint-disable-next-line #typescript-eslint/no-unsafe-member-access
Vue.prototype.$axios = axios
})
The only other way I found is to use Provide/Inject.
In my boot file, I use it like this:
export default boot(({ app }) => {
app.config.globalProperties.$axios = axios
app.provide('axios', app.config.globalProperties.$axios)
})
In any component:
setup() {
const axios: AxiosInstance = inject('axios') as AxiosInstance
[...]
}
In hindsight, I'm not quite sure if this is better than importing it.
For This worked
import {api} from 'boot/axios'
setup() {
return{
api.post('shop/', data, { #api imported that works in setup quasar
headers: {
Authorization: `token ${JSON.parse(localStorage.getItem("userData")).token}`,
'Content-Type': 'multipart/form-data'
},
onUploadProgress: function (progressEvent) {
console.log((progressEvent.loaded / progressEvent.total) * 100);
dialog.update({
message: `${(progressEvent.loaded / progressEvent.total) * 100}%`
})
}
})
}
}
I followed the example in the documentation under v2.0.0 > Read Me > Load Data (listeners automatically managed on mount/unmount) (direct link is not possible).
And replaced the connect call with the firestore specific one shown here](http://react-redux-firebase.com/docs/firestore.html#examples) in Example 1.
I copied the Todo example exactly in a new component created for testing purposes.
Todo Component:
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { firebaseConnect,firestoreConnect, isLoaded, isEmpty } from 'react-redux-firebase'
const Todos = ({ todos, firebase }) => {
// Build Todos list if todos exist and are loaded
const todosList = !isLoaded(todos)
? 'Loading'
: isEmpty(todos)
? 'Todo list is empty'
: Object.keys(todos).map(
(key, id) => (
<TodoItem key={key} id={id} todo={todos[key]}/>
)
)
return (
<div>
<h1>Todos</h1>
<ul>
{todosList}
</ul>
<input type="text" ref="newTodo" />
<button onClick={this.handleAdd}>
Add
</button>
</div>
)
}
// export default compose(
// firestoreConnect([
// 'todos' // { path: '/todos' } // object notation
// ]),
// connect((state) => ({
// todos: state.firestore.data.todos,
// profile: state.firestore.profile // load profile
// }))
// )(Todos)
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
The store configuration was configured as shown here in the docs. The store configuration was adapted to slot into the framework created by react-boilerplate.
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { fromJS } from 'immutable'
import { routerMiddleware } from 'connected-react-router/immutable'
import createSagaMiddleware from 'redux-saga'
import { reactReduxFirebase, firebaseReducer } from 'react-redux-firebase'
import { reduxFirestore, firestoreReducer } from 'redux-firestore'
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
import 'firebase/firestore'
import createReducer from './reducers'
const sagaMiddleware = createSagaMiddleware()
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
}
const rrfConfig = {
userProfile: 'users',
// useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB
// attachAuthIsReady: true
}
// Initialize Cloud Firestore through Firebase
export default function configureStore(initialState = {}, history) {
firebase.initializeApp(firebaseConfig)
// Initialize Firestore with timeshot settings
firebase.firestore()
// firebase.firestore().settings({ timestampsInSnapshots: true })
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)]
const enhancers = [
applyMiddleware(...middlewares),
// reactReduxFirebase(config), // enhancing our store with these packages
// reduxFirestore(config)
]
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle, indent */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose
/* eslint-enable */
const createStoreWithFirebase = compose(
reactReduxFirebase(firebase, rrfConfig), // firebase instance as first argument
reduxFirestore(firebase),
)(createStore)
const store = createStoreWithFirebase(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
)
// Extensions
store.runSaga = sagaMiddleware.run
store.injectedReducers = {} // Reducer registry
store.injectedSagas = {} // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers))
})
}
return store
}
I traced and verified my store configuration exactly to make sure all steps present in the documentation are configured correctly in my configuration.
My createReducer funciton is in a seperate file and you can see that I added the firebaseReducer and firebaseReducer correctly.
import { combineReducers } from 'redux-immutable'
import { connectRouter } from 'connected-react-router/immutable'
import { firebaseReducer } from 'react-redux-firebase'
import { firestoreReducer } from 'redux-firestore'
import history from 'utils/history'
import languageProviderReducer from 'containers/LanguageProvider/reducer'
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
firebase: firebaseReducer,
firestore: firestoreReducer,
language: languageProviderReducer,
...injectedReducers,
})
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history)
return mergeWithRouterState(rootReducer)
}
My redux store contains the firestore and firebase and it is injected into the component props.
What does not work is the use of connectFirestore HoC to automatically retrieve and inject a list of documents in to the component.
This is the error message:
react-dom.development.js?61bb:20266 Uncaught TypeError: Cannot read property 'ordered' of undefined
at Function.eval [as mapToProps] (index.js?d834:49)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at Function.detectFactoryAndVerify (wrapMapToProps.js?1817:63)
at mapToPropsProxy (wrapMapToProps.js?1817:54)
at handleFirstCall (selectorFactory.js?805c:37)
at pureFinalPropsSelector (selectorFactory.js?805c:85)
at Object.runComponentSelector [as run] (connectAdvanced.js?48b8:43)
at Connect.initSelector (connectAdvanced.js?48b8:195)
at new Connect (connectAdvanced.js?48b8:136)
at constructClassInstance (react-dom.development.js?61bb:11315)
(Snipped from my code which is the example 1 in documentation):
export default compose(
firestoreConnect(['todos']), // or { collection: 'todos' }
connect((state, props) => ({
todos: state.firestore.ordered.todos
}))
)(Todos)
I inspected the state variable and it does contain the firestore attribute. This attribute contains a number of functions, as expected, but it is missing the query results under "ordered", which is undefined.
I have tried all different ways to use firestoreconnect e.g. using a Class-based component, using a query with parameters, etc. and all give the same error.
My Firebase project is configured correct as I am able to create documents inside collections. A todos collection for testing purposes is present as well containing 2 documents.
I have come across this post, which mentions the following:
If you just upgraded to React-Redux v6, it's because react-redux-firebase is not compatible with v6.
See https://github.com/prescottprue/react-redux-firebase/issues/581 for details.
This does not apply to me because I am using react-redux version 5. Here are the versions I am using:
"firebase": "^5.10.1",
"react-redux": "^5.0.7",
"react-redux-firebase": "^2.2.6",
"redux": "^4.0.1",
"redux-firestore": "^0.7.3",
I have spent a significant amount of time on this. Like I said, using firestore to add new data to collections works fine. It is just this HoC business that is failing no matter how i approach the solution.
any help would be appreciated.
Never solved this. I guess it is related to incompatible versions. What I ended up doing is download v4 of react-boilerplate and set up v3 react-redux-firebase which uses the Context API as opposed to store enhancers. Now works very well.