Can i dynamically call a getter using mapGetters in Vue/Vuex? - javascript

My component template:
<template>
<section class="stage my-5">
<div class="stage-heading">
<h3 class="stage-number mb-4">Stage {{stage}}</h3>
<h6 class="stage-hrs">Total Hrs: {{totalHours}}</h6>
</div>
<div class="stage-courses card text-white bg-info mb-3" v-for="course in courses" :key="course.id">
<div class="card-header">Stage {{course.stage}}</div>
<div class="card-body">
<h5 class="card-title">{{course.title}}</h5>
<p class="card-text">{{course.creator}}</p>
<p class="card-text">{{course.hours}} Hours</p>
</div>
</div>
</section>
</template>
The state in my Vuex store:
const state = {
roadmapStage1: [],
roadmapStage2: [],
roadmapStage3: [],
};
I have getters in my Vuex store that look like:
getRoadmapStage1: state => state.roadmapStage1,
getRoadmapStage2: state => state.roadmapStage2,
getRoadmapStage3: state => state.roadmapStage3,
I'm trying to dynamically call one of these getters from a component, which one depends on a prop of the component:
export default {
name: "Stage",
data() {
return {
courses: []
}
},
props: ['stage'],
computed: mapGetters({courses: 'getRoadmapByStage'})
}
Is there any way to insert the prop into the 'getRoadmapByStage'? e.g. so it functions like
getRoadmapByStage${stage}?
Ultimately i'm trying to get the component to re-render anytime one the roadmapStage arrays are updated. Thanks!

I would suggest using a getter with a parameter for the stage id/number that returns the roadmap you want, like so:
// in getters.js
//gets the current roadmap based on the stage number
getRoadmapByStage: (state) => (stageNumber) => {
return state["roadmapStage" + stageNumber];
}
now in your component you can have:
computed: {
currentRoadmap() {
// now we'll pass in your 'stage' prop to get the appropriate map
// this will re-render the component as that prop changes
return this.$store.getters.getRoadmapByStage(this.stage);
}
}

You can declare your computed roadmap property as follows:
computed: {
roadmap() {
return this.stage ? this.$store.getters['getRoadmapByStage' + this.stage] : undefined
},
}
That way you are getting the roadmap by the value of the prop or undefined if the prop is not set to anything.

Related

What is Best way to rerender the child component when View's Object array changed

I tried this.$forceupdate() , v-if hack but it didn't work. I am new to Vue.
<template>
<div class="intro-y grid grid-cols-12 gap-3 sm:gap-6 mt-5">
<HeroCard v-for="hero in heroes" :key="hero.id" :hero="hero" />
</div>
</template>
<script>
import HeroCard from "#/components/hero/HeroCard.vue";
export default {
inject: ["heroStats"],
name: "HeroList",
components: {
HeroCard,
},
data() {
return {
heroes: this.heroStats,
};
},
methods: {
filterHeroes(heroStats, primary_attribute, attack_type, roles, name) {
if (!primary_attribute.length) {
this.heroes = heroStats;
} else {
this.heroes = heroStats.filter((hero) =>
primary_attribute.includes(hero.primary_attr)
);
...etc
}
},
},
};
</script>
When Checkboxes are checked the HeroCard component should display heroes that including the primary attributes[ 'Strength', 'Intelligence' ]
I would make heroes to a computed property and filter it with a selected_attributes data property bound to your checkboxes.
data() {
return {
heroStats: heroStats,
selected_attributes: ['Strength', 'Intelligence']
}
},
computed: {
heroes() {
return this.heroStats.filter((hero) =>
this.selected_attributes.includes(hero.primary_attr)
);
}
}
Then the heroes list will be auto-updated, when the checkboxes and selected_attributes changes. This ten will automatically trigger the update of your heroes card list.
<HeroCard v-for="hero in heroes" :key="hero.id" :hero="hero" />
This is the simplest and most Vue style solution from my point of view.
The Vue reactivity do all the work and you don't need to trigger update of your child components.
you can set a flag and when the Object Array changes set that flag to true (visa versa)
and then use a v-if to render that component only when that flag is set to true
something like
<div class="intro-y grid grid-cols-12 gap-3 sm:gap-6 mt-5">
<HeroCard v-if="showComponent" v-for="hero in heroes" :key="hero.id" :hero="hero" />
</div>
</template>```

Why not my vue component not re-rendering?

I have a question why not this component, not re-rendering after changing value so what I'm trying to do is a dynamic filter like amazon using the only checkboxes so let's see
I have 4 components [ App.vue, test-filter.vue, filtersInputs.vue, checkboxs.vue]
Here is code sandbox for my example please check the console you will see the value changing https://codesandbox.io/s/thirsty-varahamihira-nhgex?file=/src/test-filter/index.vue
the first component is App.vue;
<template>
<div id="app">
<h1>Filter</h1>
{{ test }}
<test-filter :filters="filters" :value="test"></test-filter>
</div>
</template>
<script>
import testFilter from "./test-filter";
import filters from "./filters";
export default {
name: "App",
components: {
testFilter,
},
data() {
return {
filters: filters,
test: {},
};
},
};
</script>
so App.vue that holds the filter component and the test value that I want to display and the filters data is dummy data that hold array of objects.
in my test-filter component, I loop throw the filters props and the filterInputs component output the input I want in this case the checkboxes.
test-filter.vue
<template>
<div class="test-filter">
<div
class="test-filter__filter-holder"
v-for="filter in filters"
:key="filter.id"
>
<p class="test-filter__title">
{{ filter.title }}
</p>
<filter-inputs
:value="value"
:filterType="filter.filter_type"
:options="filter.options"
#checkboxChanged="checkboxChanged"
></filter-inputs>
</div>
</div>
<template>
<script>
import filterInputs from "./filterInputs";
export default {
name: "test-filter",
components: {
filterInputs,
},
props:{
filters: {
type: Array,
default: () => [],
},
value: {
type: Array,
default: () => ({}),
},
},
methods:{
checkboxChanged(value){
// Check if there is a array in checkbox key if not asssign an new array.
if (!this.value.checkbox) {
this.value.checkbox = [];
}
this.value.checkbox.push(value)
}
};
</script>
so I need to understand why changing the props value also change to the parent component and in this case the App.vue and I tried to emit the value to the App.vue also the component didn't re-render but if I check the vue dev tool I see the value changed but not in the DOM in {{ test }}.
so I will not be boring you with more code the filterInputs.vue holds child component called checkboxes and from that, I emit the value of selected checkbox from the checkboxes.vue to the filterInputs.vue to the test-filter.vue and every component has the value as props and that it if you want to take a look the rest of components I will be glad if you Did.
filterInpust.vue
<template>
<div>
<check-box
v-if="filterType == checkboxName"
:options="options"
:value="value"
#checkboxChanged="checkboxChanged"
></check-box>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
methods: {
checkboxChanged(value) {
this.$emit("checkboxChanged", value);
},
},
}
</script>
checkboxes.vue
<template>
<div>
<div
v-for="checkbox in options"
:key="checkbox.id"
>
<input
type="checkbox"
:id="`id_${_uid}${checkbox.id}`"
#change="checkboxChange"
:value="checkbox"
/>
<label
:for="`id_${_uid}${checkbox.id}`"
>
{{ checkbox.title }}
</label>
</div>
</div>
<template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
}
methods: {
checkboxChange(event) {
this.$emit("checkboxChanged", event.target.value);
},
},
};
</script>
I found the solution As I said in the comments the problem was that I'm not using v-model in my checkbox input Vue is a really great framework the problem wasn't in the depth, I test the v-model in my checkbox input and I found it re-render the component after I select any checkbox so I search more and found this article and inside of it explained how we can implement v-model in the custom component so that was the solution to my problem and also I update my codeSandbox Example if you want to check it out.
Big Thaks to all who supported me to found the solution: sarkiroka, Jakub A Suplick

In React/Redux app why is my props.property in child component showing undefined in console log

I have built a Flashcard app and decided to implement Redux. On componentMount() it is running a getRandomCard action which sets the state of the randomCard.
My redux state shows the correct state of the random card which changes randomly with each refresh however when I pass the props to a child card component to display the question, answer and graphic the props.randomCard.question etc shows as undefined.
I don't know why. Here is the code for my reducer.
import CARD_DATA from '../components/card.data'
const INITIAL_STATE = {
cards: CARD_DATA,
randomCard: {},
seenCard: [],
endOfDeck: false
}
const cardReducer = function cards(state = INITIAL_STATE, action) {
switch (action.type) {
case 'GET_RANDOM_CARD':
return {
...state,
randomCard: [action.currentCards[Math.floor((Math.random() * action.currentCards.length))]]
}
default: return state
}
}
export default cardReducer
Here is the action:
export function getRandomCard(currentCards) {
return {
type: 'GET_RANDOM_CARD',
currentCards: currentCards,
}
}
enter code here
enter code here
Here is the parent componentDidMount method:
class App extends Component {
componentDidMount() {
const {getRandomCard, cardData} = this.props
getRandomCard(cardData)
}
And here is the child component which is trying to render the props:
return (
<div className="card-container">
<div className="card">
<div className="front">
<div className="question">{props.randomCard.question}</div>
<div className="image">
<img src={props.randomCard.imageUrl} alt='graphic not available' />
</div>
</div>
<div className="back">
<div className="answer">{props.randomCard.answer}</div>
</div>
</div>
</div>
)
}
export default Card
Props.randomCard console.logs this:
0: {id: 3, question: "What is immutablity?", answer: "When an object or an array does not get mutated when being passed to a function", imageLink: "https://miro.medium.com/max/600/1*2N0l3bLqaBgmOSIay-uc5w.png", priority: 5}
length: 1
__proto__: Array(0)
I have tried accessing these props using props.randomCard.question and props.randomCard[0].question all to no avail and I am blocked.
I have figured out why I was getting props.randomCard[0].question undefined. The component was rendering before it received the props.
I fixed issue by adding the following conditional statement:
render () {
if (this.props.card[0]) {
return (
<div className="card-container">
<div className="card">
<div className="front">
<div className="question">{this.props.card[0].question}</div>
<div className="image">
<img src={this.props.card[0].imageLink} alt='graphic not available' />
</div>
</div>
<div className="back">
<div className="answer">{this.props.card[0].answer}</div>
</div>
</div>
</div>
)
} else {
return null
}
}
}

How to init a component with the value of a VueX store field?

I plugged a VueX store to a Vue app.
How should I handle the value of fields in a form.
When the form init, it should use the value from the store, but then it shouldn't try to update the value of the immutable state.
Before I was using v-model but there I am a bit lost.
I tried something like that:
computed: mapState(["profile"])
data() {
return {
firstname: '',
};
},
created() {
this.firstname = this.profile.firstname;
}
But then each time I reopen the component, it doesn't update the value from the store.
This solution is not what I want either, because I want the store to be updated with the value from the server and not the value being currently edited.
Initially the state value is empty, try to watch it and update your data object property based on that value :
computed: mapState(["profile"]),
watch:{
profile(val) {
this.firstname = val.firstname;
}
},
mounted() {
this.firstname = this.profile.firstname;
},
This is how I did it:
On component mount, I set the field value to the component state
Then I plug v-model to the state and use an action to update the store on submit.
<template>
<div id="login" class="cModal">
<div>
<header>
<h2>Edit Profile</h2>
</header>
<div>
<form #submit="edit()">
<div class="input-group">
<label for="firstname">Firstname</label>
<input id="firstname" type="text" v-model="firstname"/>
</div>
<div class="input-group">
<button>Edit profile</button>
</div>
</form>
</div>
<footer class="cf">
Fermer [x]
</footer>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: mapState(["profile"]),
data() {
return {
firstname: '',
};
},
created() {
this.firstname = this.profile.firstname;
},
methods: {
edit() {
this.$emit("handleProfileUpdate", {firstname: this.firstname});
},
closeModal() {
this.$emit("close");
},
}
};
</script>

Using sync modifier between Parent and Grandchildren Vue 2

Problem
Let's say I have a vue component called:
Note: All vue components has been simplified to explain what I'm trying to do.
reusable-comp.vue
<template>
<div class="input-group input-group-sm">
<input type="text" :value.number="setValue" class="form-control" #input="$emit('update:setValue', $event.target.value)">
<span>
<button #click="incrementCounter()" :disabled="disabled" type="button" class="btn btn-outline-bordercolor btn-number" data-type="plus">
<i class="fa fa-plus gray7"></i>
</button>
</span>
</div>
</template>
<script>
import 'font-awesome/css/font-awesome.css';
export default {
props: {
setValue: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
},
methods: {
incrementCounter: function () {
this.setValue += 1;
}
}
}
</script>
Then in a parent component I do something like this:
subform.vue
<div class="row mb-1">
<div class="col-md-6">
Increment Value of Num A
</div>
<div class="col-md-6">
<reuseable-comp :setValue.sync="numA"></reuseable-comp>
</div>
</div>
<script>
import reusableComp from '../reusable-comp'
export default {
components: {
reusableComp
},
props: {
numA: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
}
</script>
then lastly
page_layout.vue
<template>
<div>
<subform :numA.sync="data1" />
</div>
</template>
<script>
import subform from '../subform.vue'
export default {
components: {
subform
},
data() {
return {
data1: 0
}
}
}
</script>
Question
So, how do I sync a value between reusable-comp.vue, subform.vue, and page_layout.vue
I'm using reuseable-comp.vue is many different places. I'm using subform.vue only a couple times in page_layout.vue
And I'm trying to use this pattern several times. But I can't seem to get this to work. The above gives me an error:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "numA"
Okay I found a solution that worked.
In subform.vue, we change:
data() {
return {
numA_data : this.numA
}
}
So we now have reactive data to work with. Then in the template, we refer to that reactive data instead of the prop:
<reuseable-comp :setValue.sync="numA_data"></reuseable-comp>
Then finally we add a watcher to check if the reactive data gets changed, and then emit to the parent:
watch: {
numA_data: function(val) {
this.$emit('update:numA', this.numA_data);
}
}
Now all values from grandchildren to parent are synced.
Update (4/13/2018)
I made new changes to the reusable-comp.vue:
I replaced where it says 'setValue' to 'value'
I replaced where it says 'update:value' to 'input'
Everything else says the same.
Then in subform.vue:
I replaced ':setValue.sync' to 'v-model'
v-model is two way binding, so I made use of that where it needed to be. The sync between the parent-child (not child to grandchild), is still using sync modifier, only because the parent has many props to pass. I could modify this where I could group up the props as a single object, and just pass that.

Categories

Resources