I have this gtag (analytics plugin) that I can access on my components but never on my store.
I would appreciate any opinions. Thanks
plugins/vue-gtag.js
import Vue from "vue"
import VueGtag from "vue-gtag"
export default ({ app }, inject) => {
Vue.use(VueGtag, {
config: {
id: process.env.ga_stream_id
}
})
}
store/gaUserProperty.js
import Vue from "vue"
import { User } from "~/models/user/User"
export const states = () => ({})
const getterObjects = {}
const mutationObjects = {}
Object.keys(states).forEach(key => {
getterObjects[key] = state => state[key]
mutationObjects[key] = (state, value) => (state[key] = value)
})
export const state = () => states
export const getters = { ...getterObjects }
export const mutations = { ...mutationObjects }
export const actions = {
async sendUserProperties({ dispatch, commit }) {
let res = await this.$UserApi.getUser()
if (!(res instanceof User)) {
} else {
// I can access this on my components and pages but for some reason not here....
console.log(this.$gtag)
}
}
}
To import this properly, I would export the instance (or any of its internals) from main.(ts|js):
const Instance = new Vue({...whatever});
// only export what you need in other parts of the app
export const { $gtag, $store, $t, $http } = Instance;
// or export the entire Instance
export default Instance;
now you can import it in your store:
import Instance from '#/main';
// or:
import { $gtag } from '#/main';
// use Instance.$gtag or $gtag, depending on what you imported.
As other answers mentioned, in current Vuex version the Vue instance is available under this._vm inside the store. I'd refrain from relying on it, though, as it's not part of the exposed Vuex API and not documented anywhere. In other words, Vuex developers do not guarantee it will still be there in future versions.
To be even more specific, Vue's promise is that all v2 code will work in v3. But only if you use the exposed API's.
And the discussion here is not even on whether it will be removed or not (it most likely won't). To me, it's more a matter of principle: if it starts with a _ and it's not documented, it translates into a message from authors: "We're reserving the right to change this at any time, without warning!"
You can access the Vue instance through this._vm in the Vuex store, so you would just need to do:
console.log(this._vm.$gtag)
That should do the trick.
According to nuxtjs the plugins are available in the store actions.
https://nuxtjs.org/docs/directory-structure/plugins
Sometimes you want to make functions or values available across your
app. You can inject those variables into Vue instances (client side),
the context (server side) and even in the Vuex store. It is a
convention to prefix those functions with a $.
Related
In Vue 2 I used to import Vue and access global properties like this (from the store):
import Vue from 'vue'
Vue.config.myGlobalProperty
According to the new documentation, in Vue 3 the global properties are declared using the app object returned by createApp:
const app = createApp({})
app.config.globalProperties.myGlobalProperty
And then accessed in the child component by simply calling this.myglobalProperty
But how to access that global property from the store? I tried exporting/importing the app object but it doesn't work (probably due to the app being created after its import in the store).
With Vue 2 I used to use global properties in the store like this:
Declaration in the main.js file:
import Vue from 'vue'
Vue.config.myglobalProperty = 'value'
Usage in the store:
import Vue from 'vue'
Vue.config.myglobalProperty
Is there a good way to do that in Vue3?
I noticed a better way to provide/inject properties but it works with child component only and not with the store.
You could pass the app instance to a store factory:
// store.js
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
actions: {
doSomething() {
if (app.config.globalProperties.myGlobal) {
//...
}
}
}
})
}
And use it in main.js like this:
// main.js
import { createApp } from 'vue'
import { createStore } from './store'
const app = createApp({})
const store = createStore(app)
app.use(store)
app.mount('#app')
If your store modules need access to the app instance, you could use the same technique above with a module factory:
// moduleA.js
export default (app) => {
return {
namespaced: true,
actions: {
doSomething() {
if (app.config.globalProperties.myOtherGlobal) {
//...
}
}
}
}
}
And import it into your store like this:
// store.js
import moduleA from './moduleA'
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
modules: {
moduleA: moduleA(app),
}
})
}
If you do not use Vuex, etc., you can easily create your store via provide/inject on the application itself, as in the example (the example is simplified for understanding):
const createState = () => reactive({counter: 0, anyVariable: 1});
const state = createState();
const key = 'myState';
// example of reactivity outside a component
setInterval(() => {
state.counter++;
}, 1000);
const app = createApp({});
app.provide('myState', state); // provide is used on the whole application
As you can see, your own store can be completely used outside the component.
Inside components, you can use (example):
setup() {
...
const globalState = inject('myStore'); // reactive => {counter: 0, anyVariable: 1}
...
return {globalState, ...}
}
Accordingly, you can have multiple stores in multiple Vue applications.
I hope this example will help you somehow.
Large SPA Vuex question - is there a way to abstract common methods that can be inherited or injected into Vuex namespace modules? What I have below works, but feels kludgy as I have to standardize states, etc. I could pass in assoc. array keys to accommodate different state stores, but looking to streamline. Ive look at Vuex plugins and I think there is way to use that to some degree, but again not ideal if I wanted something as simple as getById()
Another approach is to create a Vue IoC factory container, Vue provider, with a Vuex driver, services for for common components, but that is a lot of overhead and I feel would be an overkill, but maybe that is the best approach for a large SPA. Would appreciate some guidance as to where this is the right approach.
vuex-common.js
Collection of common service methods. I could create a common for getters, mutations, actions, etc.
import isArray from 'lodash/isArray'
export function getById () {
return state => {
const find = val => state.data.find(x => x.id === val)
return val => isArray(val) ? val.map(find) : find(val)
}
}
Namespace store example -- store/modules/Users.js
// removed other imports for brevity sake
import { getById } from 'vuex-common'
import forEach from 'lodash/forEach'
import {fetchUsers} from '~/api/apis/users.api';
const initialState = () => ({
data: []
});
const state = initialState();
const getters = {
getById: getById() // vuex-common
// removed other getters for brevity sake
}
const actions = {
async getUsers({commit, getters}) {
try {
const {data} = await fetchUsers();
forEach(data, (u) => {
/* Import getById vuex-common function */
if (!getters.getById(u.id)) {
commit('ADD_USER', u);
}
});
} catch (e) {
console.error("ERROR", e.message, e.name);
}
},
}
// removed mutations and exports for brevity sake
I have a mutation can be reused across multiple vuex modules but modifies the state at a module level. How can the mutation be separated out so that it can be dropped into each module's mutations without having to repeat the code?
const state = {
fieldInfo: {}
}
const actions = {
async getOptions({ commit }) {
commit('setOptions', await Vue.axios.options('/'))
}
}
const mutations = {
setOptions(state, value) {
// long mutation happens here
state.fieldInfo = value
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
As you already have your stores namespaced this should work perfectly. All you need to do is move the mutation function to it's own file and then import it in the stores that you need it.
export default function (state, value) {
// long mutation happens here
state.fieldInfo = value
}
Then in your store
import setOptions from './setOptions.js'
const state = {
fieldInfo: {}
}
const actions = {
async getOptions({ commit }) {
commit('setOptions', await Vue.axios.options('/'))
}
}
const mutations = {
setOptions
}
export default {
namespaced: true,
state,
actions,
mutations
}
Someone will probably have a better answer. But in its current state 'fieldInfo' is a shared vuex property. It's like saying window.somVar and expecting to have someVar be a different instance depending on what module is using it. That's not really possible without declaring a new instance of a class, etc. At the most basic level you would need something more like fieldInfo{moduleA: val, moduleB: val}
I have generated a project using Create React App and then added Redux.
The redux state is then split into three parts that each has its own reducer and some middleware defined. The reducers are place in files called part1.js part2.js part3.js there is then a common.js file that imports the reducer and the middleware from part1-2-3.js and adds them to combineReducer and applyMiddeware.
My question is if there is anyway to not having to import everything in one place. What I want is to be able to add the reducer and middeware to comineReducer and applyMiddleware from within part1-2-3.js, the reason is to get rid of an explicit common boilerplate code file in common.js. Is this possible or is the only way to import everything into one place?
UPDATE
I have now great examples on how to solve the combineReducer part, however I still need to do something similar for applyMiddleware. I have found an example from the following repo on how to do something similar with applyMiddleware. However its in TypeScript and I have a hard time translating it into what is the minimal way to get this working within a JS React/Redux application. Would be great with some examples.
UPDATE
So I finally found this minimal library doing what I want.
Yes! I have a reducer registry which is similar (almost identical) to this reducer manager: https://redux.js.org/recipes/code-splitting#using-a-reducer-manager:
const DEFAULT_REDUCER = state => state || null;
export class ReducerRegistry {
constructor() {
this._emitChange = null;
this._reducers = {};
}
getReducers() {
// default reducer so redux doesn't complain
// if no reducers have been registered on startup
if (!Object.keys(this._reducers).length) {
return { __: { reducer: DEFAULT_REDUCER } };
}
return { ...this._reducers };
}
register(name, reducer, options = {}) {
if (this._reducers.name && this._reducers.name !== reducer) {
throw new Error(`${name} has already been registered`);
}
this._reducers = { ...this._reducers, [name]: { reducer, options } };
if (this._emitChange) {
this._emitChange(this.getReducers());
}
}
setChangeListener(listener) {
this._emitChange = listener;
}
}
const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;
Then I have my redux domains organized into folders like reducks-style: https://github.com/alexnm/re-ducks
In the index.js of the reducks domain, I import the reducer registry and register the reducer:
import Domain from './name';
import reducer from './reducer';
import { reducerRegistry } from '...wherever';
reducerRegistry.register(Domain, reducer); // register your reducer
export { ... whatever }
Finally, my store uses the reducer registry like this:
export const store = createStore(
combine(reducerRegistry.getReducers()),
initialState,
composeEnhancers(applyMiddleware(...middlewares))
);
// Replace the reducer whenever a new reducer is registered (or unregistered).!!!!
// THIS IS THE MAGIC SAUCE!
reducerRegistry.setChangeListener(reducers =>
store.replaceReducer(combine(reducers))
);
export default store;
This setup has worked magically for us. Allows us to keep all of our redux logic very isolated from the rest of the application (and from other redux domain logic!), works fantastic for code-splitting. Highly recommend it.
Yes you can. We use it with dynamic import and works well. We use with react hooks.
use store.replaceReducer https://redux.js.org/api/store#replacereducernextreducer
in configureStore (or any file when you call createStore from redux)
const store = createStore(/*...*/)
add
store.injectedReducers = {}; // Reducer registry
and create a new file with injectReducer hook.
const useInjectReducer reducer => {
const store = useStore();
const key = Object.keys(reducer)[0];
if (
Reflect.has(store.injectedReducers, key) &&
store.injectedReducers[key] === reducer[key]
) {
return;
}
store.injectedReducers = {
...store.injectedReducers,
...reducer
};
store.replaceReducer(combineReducers(store.injectedReducers));
}
and you can use it in react App:
export const TodoPage = () => {
useInjectReducer({ [REDUCER_NAME]: TodoReducer });
/*...*/
}
if you use server side rendering you need to be sure redux not cleaning up the states for missing reducers before dynamic import. You can create a dummyReducers to prevent that.
const dummyReducers = Object.keys(initialState).reduce((acc, current) => {
acc[current] = (state = null) => state
return acc;
}, {});
and add this for:
const store = createStore(
combineReducers(dummyReducers),
initialState
)
We use the same pattern to Inject Sagas.
Hi this question is a continue to this one!
I'm getting my routes dynamically via an ajax request (following this article in the official docs "Declaring resources at runtime"), I'm using an async function to return a list of resources from an ajax request.
What is the best way to dispatch an action to store meta data, which I got form ajax request in redux, for later access?
Also when user has not yet logged in, this function will not return anything, after logging in, user will have access to a couple of resources. What is the best way to reload resources?
The best option is to use redux-saga. https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html
Then
export function* async() {
yield fetch(); //your Ajax call function
yield put({ type: 'INCREMENT' }) //call your action to update your app
}
Incase you can't use redux-saga, I like your solution with private variable. You should go ahead with that.
To get this to work, I added a private variable, which I store the data mentioned in the question, and I access it via another function, which I exported from that file.
This gives me what I need, but I don't know if it's the best way to go.
https://github.com/redux-utilities/redux-actions
redux-actions is really simple to setup. Configure the store and then you can setup each state value in a single file:
import { createAction, handleActions } from 'redux-actions'
let initialState = { myValue: '' }
export default handleActions({
SET_MY_VALUE: (state, action) => ({...state, myValue: action.payload})
})
export const setMyValue = createAction('SET_MY_VALUE')
export const doSomething = () => {
return dispatch => {
doFetch().then(result => {
if (result.ok) dispatch(setMyValue(result.data))
})
}
}
Then in your component you just connect and you can access the state value
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
render = () => (
<span>{this.props.myValue}</span>
)
}
MyComponent.propTypes = {
myValue: PropTypes.string.isRequired
}
const mapStateToProps = (state) => ({
myValue: state.myState.myValue
})
const mapDispatchToProps = () => ({})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)