I am currently building a small Vue JS app. I'm using a Simple State Management with Reactivity API to cache data between pages. But I also have some functions built in. One function is to fetch data from the backend. For the API communication I created a helper class that talks to the API. For example, a product list should be displayed. The component has a method that calls a store.js method. The store.js method then calls a method from the helper class. I work with fetch and async await. To display a list in the component view at the end of the day I need to pass the data from the helper class to the component using asnyc await. Now my question. Is this approach a crime against javascript? Is there a better solution for this?
The three relevant files.
// PageComponent
<script>
import { store } from '../helpers/store'
export default {
data() {
return {
store,
list: [],
}
},
methods: {
getData() {
store.getData()
}
},
}
</script>
// store.js
import { reactive } from 'vue'
import { comhelper } from "./comhelper";
export const store = reactive({
brand: "",
async getData() {
return await comhelper.getData();
}
});
// comhelper.js
export const comhelper = {
async getSurveyData() {
const d = await fetch('/data/db.json');
return await d.json();
}
}
Related
When in preview mode (#see https://nuxtjs.org/docs/features/live-preview) Vuex getters do not appear to work, at least with data that's fetched.
Accessing the data from the store directly works.
Accessing the data via a getter which then calls the same code from the store, returns undefined.
Data is fetched (technically, using axios) via nuxtServerInit, but the issue is the same if I change that to fetch (using axios) via asyncData from within my template.
These are two simple examples (using actual code that exhibits this issue)
This only happens when preview mode is enabled and using target: static
enablePreview()
Why? Why don't getters work? It's like the state that the getters are accessing is different to the state that I access when I access it directly.
Example 1:
/store/index.js
export const getters = {
getURLData(state) {
return state?.urls ?? [];
},
getPageData: (state, getters) => (url) => {
const a = state?.urls; // [array,of,urls]
const b = getters.getURLData; // undefined
...
}
Example 2:
store/index.js
export const getters = {
getSettings(state) {
return state?.settings;
}
}
whatever.vue
export default {
head() {
const a = this.$store.getters.getSettings; // undefined
const b = this.$store.state?.settings; // {data}
}
}
I think that the problem is in your way of calling vuex getters, try to use MapGetters instead of accessing it directly via this.$store.
import { mapGetters } from 'vuex'
export default {
.....
computed: {
...mapGetters({
settings: 'getSettings',
}),
}
....
}
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 $.
For my project I'm using data from https://api.randomuser.me/ which basically returns a random person every time you fetch the data.
I've created a component (for the purpose of the question I've simplified it) that returns a first name of person fetched by the API. However, if I use the component twice I get two different names all the time. How can I reuse the component and customise it (using props etc.) while using the same dataset which then refreshes every 10 seconds as in the code below?
import React,{Component} from "react";
import { makeStyles } from '#material-ui/styles';
import Budgetdata from './Budgetdata.js'
import Caption from './Caption.js'
export default class Apicall extends Component {
intervalID;
constructor(props) {
super(props);
this.state = {
loading:true,
person:[],
random: 0,
name:"name"};
}
async getdata () {
const url = "https://api.randomuser.me/";
const response = await fetch(url);
const data = await response.json();
var value = Math.round(Math.random())
this.setState({ person: data.results[0],loading:false,random: value,name:"name"})
}
async componentDidMount() {
this.getdata()
this.intervalID = setInterval(this.getdata.bind(this), 10000);
}
async componentWillUnmount() {
clearInterval(this.intervalID);
}
render = props => {
if (this.state.loading || !this.state.person) {
return (<div>loading..</div>)
}
else
{
return(
<div>{this.state.person.name.first}</div>
)
}
}
}
Based on
https://www.valentinog.com/blog/await-react/
await is not well supported at the front end side. I will suggest you to use Axios libraries.
On the other hand, if you need to use a react component twice you could need to use the react component key field as told at:
https://blog.cloudboost.io/key-concept-and-its-necessities-in-react-component-885c18084e59
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)
I am running a simple Axios call like so:
.get('https://myAPI.com/')
.then(response => {
this.info = response.data
})
And then display the data through a v-for array loop on my components. The problem is that I am running this mounted Axios call on each component I use it for. For example, I have a component for desktop screens that uses this axios call to display data in sidebar, while my mobile screen component uses the exact same axios call too display in a header.
The problem is that I am running multiple calls to the same API since each component is using the mounted axiox function.
Is there a way to run this call once and then utilize the v-for loop on each component?
Use Vuex for such task.
I'll make a very simple example.
Install vuex and axios in your project
later create a file in your project call, store.js.
store.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
const store = new Vuex.Store({
state: {
info : []
},
mutations: {
updateInfo (state, info) {
state.info = info
}
},
actions: {
fetchData({commit}) {
axios.get('https://myAPI.com/')
.then(response => {
commit('updateInfo', response.data )
})
}
}
})
in your main.js import store.js file
import store from "./store";
new Vue({
...
store,
...
});
in your App.vue dispatch 'updateInfo' action.
App.vue
...
created() {
this.$store.dispatch("fetchData");
}
...
And in the component you want to use the info data component, set:
...
computed: {
info() {
return this.$store.state.info
}
},
...
and use info to render the elements with the v-for directive.
This info refers the array of elements you bring
OK, I've found a way to handle this without Vuex. My example: I have two components TrainingCourseComponent and CertificateComponent.
In TrainingCourseComponent:
data() {
return {
trainings : {},
},
methods:{
loadTrainingCenters(){
axios.get("/trainingCourse")
.then(({data}) => {
this.trainings = data;
Event.$emit('globalVariables', data);
});
}
}
created(){
this.loadTrainingCenters();
}
and you can do this in any other component but in this case CertificateComponent(you can define it in mounted() or created() method it doesn't matter:
data() {
return {
training_courses:{}
}
}
mounted(){
Event.$on('globalVariables', (trainings) => {
this.training_courses = trainings;
});
}
p.s. I guess you know but just in case Event is a global Vue instance defined in app.js that I use for different kind of stuff :)
app.js
/**
* some custom event
*/
window.Event = new Vue();