How to receive dynamic data from vuex state into component - javascript

I'm trying to learn vuex but I think I am missing some basic understanding. Any advice please.
From one component I am dispatching the scale value of my zoomable map to vuex store.
Store.js
export default new Vuex.Store({
state: {
scale:""
},
getters:{
MJERILO: state => {
return state.scale
}
},
mutations:{
STORESCALE: (state, payload) => {
state.scale = payload
}
},
actions:{
RECEIVECURRENTSCALE: (context, payload) => {
context.commit("STORESCALE", payload);
}
}
})
This part is working well because in vue dev tool I can see that the scale number is changing in mutation, state and getters when I do zoom in/out with my mouse. (Do, in mutation is changing automaticaly, and for state and getters I need to press load state. I guess this work like this)
So the problem is probably in the way how I am trying to receive data from vuex state into some other component.
I tried:
Map1.vue
mounted(){
var scale = this.$store.getters.MJERILO
}
But I just get the value stored in state property mjerilo (in this case empty). And I need dynamic that I sent to state.
For static data this worked perfectly (I tried with simple array).
I also tried to retry data in computed, but I have a similar problem. In this case in mounted I get just the first scale value
computed: {
mjerilo(){
return this.$store.getters.MJERILO
}
}
mounted(){
var scale = this.mjerilo
}
I am quite lost. From readings I understand that when ever I scroll my map with mouse I am sending data to action for "registration", than through mutation I am storing this data in state. From state I can get the last updated data (in this case scale) in any other vue component of my app?
UPDATE: I am adding Map1.vue component
<template>
<svg-map name="Zupanije" ></svg-map>
</template>
<script>
import * as d3 from 'd3'
import SvgMap from "./SvgMap"
export default {
name: "Map1",
components: {
"svg-map":SvgMap
},
mounted(){
......lots of javascrip code
.
.
var scale = this.$store.getters.MJERILO
}
}
</script>

I believe you are looking for a function like watch
data: {
scale: this.$state.scale
},
watch: {
'$store.state.scale': (newVal) => {
this.scale = newVal;
}
}

I'm not sure what's wrong but you could try this, what happens then? How you dispatch your action?
<template>
<div>
{{ MJERILO }}
</div>
</template>
import { mapGetters, mapActions } from "vuex";
export default {
...mapGetters(["MJERILO"]),
}

Related

Can't access my store object from my component

In my store module /store/template.js I have:
const templateConfig = {
branding: {
button: {
secondary: {
background_color: '#603314',
background_image: ''
}
}
}
}
export const state = () => ({
branding: {},
...
})
export const actions = {
initializeStore (state) {
state.branding = templateConfig.branding
}
}
(initializeStore() is called when app initially loads)
I want to retrieve the branding the branding object in my component:
computed: {
...mapState({
branding: state => state.template.branding
})
}
But when trying to console.log() branding I see this:
Why don't I simply see the branding object? (and what on earth is this?)
You need to always use a mutation to change state. You can call one from your action:
export const mutations = {
SET_BRANDING(state, payload) {
state.branding = payload;
}
}
export const actions = {
initializeStore ({ commit }) {
commit('SET_BRANDING', templateConfig.branding);
}
}
What you're seeing with the observer is normal, and indicates that the branding object has been successfully mapped and accessed.
What you see is Vue's observable object, which is how Vue implements reactivity. Without this, there would be no reactivity, and you will see such a wrapper on all top-level reactive objects. You can pretend it's not there.
Vue in fact applies this same "wrapper" to the data object internally to make it observable:
Internally, Vue uses this on the object returned by the data function.
You won't see it on other reactive properties, but if they're reactive, they belong to some parent observable object.
You need to import { mapState, mapActions } from 'vuex' (already done I guess).
And then, you can write this
...mapState(['branding']) // or ...mapState('#namespacedModule', ['branding'])
Still, why do you not simply put the state directly (with your background_color) rather than going through a Vuex action ?
If you want to keep it this way, do not forget to await this.initializeStore() in your component before trying to access the state.

the state is undefined in components although I use mapGetters to access it

I have a store and a component. In my component and in created() I do an API fetch so my state changes and also in computed I use mapGetters so I could have access to my state. But when I do this nothing renders on the screen. I even use v-if directive to see if the value is undefined or not.
Also I should mention that in Vue devtools I can clearly see that the state updates correctly and I can see that the data is being fetch from API.
I already searched so much on the web and tried whatever I could find. Just one solution worked and it was to also have data inside my component but this solution is not a good solution due to duplication of the data (one local to component and one on the store).
So here is my code:
movies.js (store)
import axios from 'axios';
const state = {
heading: '',
movies: [],
};
const getters = {
getMovies: (state) => state.movies,
getHeading: (state) => state.heading,
};
const actions = {
async fetchTopRatedMovies({ commit }) {
const res = await axios.get(
`https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.VUE_APP_THEMOVIEDB_API_KEY}&language=en-US&page=1`
);
commit('setMovies', res.data.results);
commit('setHeading', 'Top Rated Movies');
},
};
const mutations = {
setMovies: (state, movies) => (state.movies = movies),
setHeading: (state, heading) => (state.heading = heading),
};
export default {
state,
getters,
actions,
mutations,
};
Movies.vue (component)
<template>
<div v-if="!heading || !movies">
<Spinner />
</div>
<div v-else>
<h5 class="center">{{ heading }}</h5>
<div v-for="movie in movies" :key="movie.id">
<p>{{ movie.title }}</p>
</div>
</div>
</template>
<script>
import Spinner from '../layout/Spinner';
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'Movies',
components: {
Spinner,
},
methods: {
...mapActions(['fetchTopRatedMovies']),
},
computed: {
...mapGetters(['getMovies', 'getHeading']),
},
created() {
this.fetchTopRatedMovies();
},
};
</script>
<style></style>
One way to solve it is to change computed to:
computed: {
...mapGetters({
heading: 'getHeading',
movies: 'getMovies',
}),
},
but I wonder why this has to be like this. Because in the documentation it is written as I did in the code above.
computed: {
...mapGetters({
heading: 'getHeading',
movies: 'getMovies',
}),
},
The above code is working and the reason is, in your condition you are using v-if="!heading || !movies" as well as you are assigning values to variables headings and movies in the computed properties.
But when you use code like below mentioned, as the varaibles you are using in your condition, are not declared in your app so it is not working.
If you want to write the code like below, you can directly use these getter properties getMovies and getHeading in your app (like you use data properties). So in this case in your condition if you write like v-if="!getHeading|| !getMovies" then it will work.
computed: {
...mapGetters(['getMovies', 'getHeading']),
},

In React / Redux, how to call the same fetch twice in componentDidMount, setting 2 state variables with results

The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components

Use Constant data in all Vue.js component from the API call using Mixins

I have a Mixins that get's the data from the sessionStorage, and that data is being used in all component and API call to get data is in the Main component(App.vue) that fetch the data and set's into the sessionStorage.
beforeCreate() {
if (!sessionStorage.getItem('constants')) {
axios.get('/data').then(function(response) {
sessionStorage.setItem('constants',JSON.stringify(response.data.result));
});
},
In the Mixins I'm not getting data from the sessionStorage, Because Mixins runs before the App.vue Component and that does not work.
I also tried to keep the fetch call inside the Mixins but, fetch call is going multiple times even though, I have the condition while getting data from sessionStorag.
import Vue from 'vue';
const Constants = Vue.mixin({
data() {
const constant = JSON.parse(sessionStorage.getItem('constants'));
return {
get constantsData() {
return {
COUNTRY: constant.COUNTRY,
STATE: constant.STATE,
};
},
};
},
});
export default Constants;
What is the best way to utilize Mixins with the API data. ?
You can do
beforeCreate() {
if (!sessionStorage.getItem('constants')) {
axios.get('/data').then(function(response) {
sessionStorage.setItem('constants',JSON.stringify(response.data.result));
this.constant = response.data.result;
});
}
And in mixins:
import Vue from 'vue';
const Constants = Vue.mixin({
data() {
return {
constant: {}
}
},
});
export default Constants;
But normally I prefer using vuex to share data between component.
I found the answer, I used method in the mixins. because I'm using constant as a dropdown value (ie. country name, state from the API). I want to call method only when the dropdown component is created
const Constants = Vue.mixin({
methods: {
getConstantData() {
return JSON.parse(sessionStorage.getItem('constants'));
},
},
data() {
return {};
},
});
export default Constants;
and I can use getConstantData method in any component to access the data just getConstantData()

how to update child component's property using props in vue js?

I have a addToCart component(child) on foodList component(parent). and there is another component Cart. i want to reset the addToCart component's counter value to 0 whenever i will empty my cart.
App.vue
data() {
return {
msg: "Welcome to Your Food Ordering App",
foodData:[],
cart:[],
reset:false
};
},
methods: {
emptyCart:function(){
this.reset = true;
this.cart = [];
}
}
foodList.vue
export default {
props:['foods','reset'],
data() {
return {
};
}
}
<addToCart :reset="reset"></addToCart>
addToCart
export default {
props:['food','reset'],
data(){
return {
counter:0
}
},
beforeMount() {
if(this.reset) {
this.counter = 0;
}
}
in app.vue I'm modifying the reset property to "true" and then passing it to foodList.vue, then passing it to addToCart.vue.
In addToCart.vue I'm checking if reset prop is true then set the counter to 0;
But this is not working.let me know where am I missing?
Please refer to this link for complete code.
food ordering app
So basically you want to pass the state over multiple components. There are multiple ways to achieve this. These are my three recommend ones.
Centralized State management
In order to handle states easier, you can make use of a centralized state management tool like vuex: https://github.com/vuejs/vuex
This is what I recommend you, especially when it comes to bigger applications, where you need to pass the state over multiple levels of components. Trust me, this makes your life a lot easier.
Property binding
The most basic way to communicate with your child components is property binding. But especially when it comes to multi-level communication it can get quite messy.
In this case, you would simply add counter to both of your child components props array like this:
foodList.vue (1. Level Child Component)
export default {
props:['foods','reset', 'counter'],
// ... your stuff
}
And include the component like this:
<foodList :counter="counter"></foodList>
addToCart.vue (2. Level Child Component)
export default {
props:['food','reset', 'counter'],
// ... your stuff
}
And finally include the component like this:
<addToCart :reset="reset" :counter="counter"></addToCart>
As a last step, you can specify counter in the data object of your root component and then modify it on a certain event. The state will be passed down.
App.vue
data() {
return {
// ... your stuff
counter: 0,
};
},
methods: {
emptyCart:function(){
// ... your stuff
this.counter = 0; // reset the counter from your parent component
}
}
Event Bus
As a third option, you could make use of Vue's event bus. This is the option I personally choose for applications, which get too messy with simple property binding, but still are kind of too small to make us of Centralized State management.
To get started create a file called event-bus.js and then add the following code to it:
import Vue from 'vue';
export const EventBus = new Vue();
Now you can simply trigger events from your parent Component like this:
App.vue
import { EventBus } from './event-bus.js'; // check the path
export default {
// ... your stuff
methods: {
emptyCart:function(){
// ... your stuff
EventBus.$emit('counter-changed', 0); // trigger counter-changed event
}
}
}
And then listen to the counter-changed event in your child component.
addToCart.vue
import { EventBus } from './event-bus.js';
export default {
// ... your stuff
created() {
EventBus.$on('counter-changed', newCounter => {
this.counter = newCounter;
});
}
}
Learn more about the event bus: https://alligator.io/vuejs/global-event-bus/

Categories