New to vuex. Simple pokemon SPA that should display pokemon by generation and type. I am trying to write a method-style getter that will pass a component's parameter. EDIT: moved currentType to state:
//Vuex.Store
state: {
pokemon: [],
currentType: 'all'
},
getters:{
availablePokemon: (state) => (currentType) => {
if(!currentType || currentType === 'all'){
return state.pokemon
}else{
return state.pokemon.filter(pokemon => {
//some api objects have two types
if(pokemon.types.length === 2){
if(pokemon.types[0] == currentType || pokemon.types[1] == currentType){
return true
}
}else if(pokemon.types[0] == currentType){
return true
}
})
}
},
mutations:{
changeType(state, type){
state.currentType = type
}
},
actions:{
updateType(context, type){
context.commit('changeType', type)
}}
}
EDIT:
//Pokemon.vue
<select name="type" id="type" #change="updateType($event.target.value)">
<option v-for="(type, index) in types"
:key="index"
:value="type">{{ type }}</option>
</select>
<li v-for="(pokemon, index) in allPokemon"
:key="index">
{{ pokemon.name }}
</li>
export default {
data(){
return{
types: ['all', 'bug', 'dragon'],
}
},
computed: {
allPokemon(){
//this fails, says currentType is undefined
return this.$store.getters.availablePokemon(currentType)
}
},
methods: {
updateType(type){
this.$store.dispatch('updateType', type)
}
}
The component method-style getter doesn't recognize the parameter. I've tried moving the currentType to my store (and updating it with an action => mutation => etc), but that didn't work either. Any ideas what I'm missing?
Architecturally, I'd store the currentType in the
Vuex state object. Then, you can reference the currentType in the getter function - and - also reference the currentType app wide.
JSFiddle here for your reference (see javascript tab and console output): https://jsfiddle.net/qb47x2z1/38/
Related
I have a drop-down list which is coming from the query and when I click on the option the related data should display. I have this drop-down as shown in image
.
How to display the option only once.
I have this below code:
class StoreLocator extends PureComponent {
constructor(props) {
super(props)
this.state = {
options : [],
}
}
getStoreLocatorDropdown(){
return(
<div>
<select>
<option value="" hidden>Select product/service type</option>
{
this.state.options.map((obj) => {
return <option value={obj.id} changeOption={this.handleChange}>{obj.name}</option>
})
}
</select>
</div>
)
}
handleChange(){
console.log("clicked")
}
async componentDidMount(){
let storeLocatorQuery = StoreLocatorInstance.getStoreLocator()
await fetchQuery(storeLocatorQuery).then((data) => {
this.setState({
options : data.storeLocatorLocations.items
})
this.getStoreLocatorDropdown()
},
(error) => console.log(error)
)
}
render() {
return (
<div>
<h1>Store Locator</h1>
<div>
{this.getStoreLocatorDropdown()}
</div>
</div>
)
}
}
export default StoreLocator
How to display option only once when it's values are repeated. And how to make it clickable and display its related data
To stop duplicate values from being displayed on your options list you can add an additional array(duplicateCheck) which would make sure that the values are not repeating
in your options list:
let duplicateCheck=[];
this.state.options.map((obj) => {
if(duplicateCheck.includes(obj.name))
{
return (null);
}
else
{
duplicateCheck.push(obj.name);
return <option value={obj.id} changeOption={this.handleChange}>{obj.name}</option>}
}
})
Seems like what you are trying to do is to only show the unique/distinct options in the drop down list.
One of the manual way you can resolve this is to filter your options datasource first.
In your case, it is the "this.state.options"
Inside your "componentDidMount" function, you can filter it before setting the value into your state:
var data = [
{ id: 1, name: 'Partsandservices' },
{ id: 2, name: 'Partsandservices' },
{ id: 3, name: 'Petromin' }
];
data.map(item => item.name)
.filter((value, index, self) => self.indexOf(value) === index)
// this should return you ["Partsandservices", "Petromin"]
However, this is not a recommended approach, as the root cause of this duplication should be resolved from the deepest level, which is from the "StoreLocatorInstance.getStoreLocator()".
Since the options returned are repeated name on "Partsandservices", does it contains different meaning?
Maybe Partsandservices in Location A and Partsandservices in Location B?
Or was it a mistake for returning two same names to your application?
You should check on that.
I am trying to update props child to parent with on:click $event.
I passed the data and $event to parent to child, like below.
in parent;
<v-filter
:sortTypePrice="sortTypePrice"
:sortTypeNewest="sortTypeNewest"
v-on:updatePrice="sortTypePrice = $event"
v-on:updateDate="sortTypeNewest = $event"
/>
data(){
return {
sortTypePrice: "",
sortTypeNewest: "",
}
}
computed: {
filterArticles(){
let filteredStates = this.api.filter((article) => {
return (this.keyword.length === 0 || article.address.includes(this.keyword))
});
if(this.sortTypePrice == "price") {
filteredStates = filteredStates.sort((prev, curr) => prev.price1 - curr.price1);
}
if(this.sortTypeNewest == 'created_at') {
filteredStates = filteredStates.sort((prev, curr) => Date.parse(curr.created_at) - Date.parse(prev.created_at));
}
return filteredStates;
},
}
I got the props and set the $event update. But my #click is not working.
in child
<ul>
<li v-model="sortPrice" #click="updatePrice" :value="price">lowest</li>
<li v-model="sortDate" #click="updateDate" :value="created_at">newest</li>
</ul>
props:["sortTypePrice", "sortTypeNewest"],
name: "controller",
data(){
return {
price: "price",
created_at: "created_at",
sortPrice:this.sortTypePrice,
sortDate:this.sortTypeNewest,
};
},
methods: {
updatePrice(e){
this.$emit("updatePrice", e.target.value)
},
updateDate(e){
this.$emit("updateDate", e.target.value)
}
}
I think, I am using very wrong way to do this. if it so, what is the right way to achieve this?
You should not set both :value and v-model. You can try
<ul>
<li #click="$emit('updatePrice', 'price')" :value="price">lowest</li>
<li #click="$emit('updateDate', 'created_at')" :value="created_at">newest</li>
</ul>
I find the following the best way to sync a prop between parent and child component:
in parent:
<!-- notice `sync` modifier -->
<child :foo.sync="val" />
in child:
<input v-model="foo_" />
props: ['foo'],
computed: {
// create a local proxy for the `foo` prop
foo_{
// its value should be the value of the prop
get(){
return this.foo
},
// when we try to change it we should update the prop instead
set(val){
this.$emit('update:foo', val)
}
}
}
Now in the child component you can work with the foo_ prop the same way as you would with the foo prop. Whenever you try to change it, it will update the foo prop in the parent and then sync down so that foo_ remains equal to foo. For example this.foo_ = 1 would make foo == 1.
This is the same pattern that is applied with the v-model directive. Check .sync Modifier for better understanding.
I have this vue component:
<template>
<div id="OrderTypeSelect" class="order-type-select">
<b-form-select v-model="curDocType" :options="options" class="mb-3">
</b-form-select>
</div>
</template>
the value of the select input is bound to the Vuex store like this:
computed: {
curDocType: {
get () {
return this.$store.state.curDocType
},
set (value) {
this.$store.commit('setcurDocType', value)
}
}
}
What I can't figure out is how to conditionally prevent the select value from changing. I've tried this:
computed: {
curDocType: {
get () {
return this.$store.state.curDocType
},
set (value) {
if (this.$store.state.curOrder == ""){
this.$store.commit('setcurDocType', value)
}
else{
console.log("Debe descartar");
this.curDocType.get() //This throws error!
}
}
}
}
Even if I don't commit the value to the store, the value in the input field is changed.
I need to call get() again (or something else) to make this binding persistent when my condition is triggered:
if (this.$store.state.curOrder != "") {
//Do not modify store and return the input selection to it's previous value
}
In your case i recommend to use a data object item called curDocType and watch it instead of using computed property :
<b-form-select v-model="curDocType" :options="options" class="mb-3">
data object :
data(){
return{
curDocType:this.$store.state.curDocType
};
}
watch property :
watch:{
curDocType(newval,oldval){
if (this.$store.state.curOrder == ""){
this.$store.commit('setcurDocType', newval)
}else{
this.$nextTick(() => {this.curDocType=this.$store.state.curDocType})
}
}
}
Try <b-form-select v-model="curValue" #input="setValue" :options="options" class="mb-3">
Where curValue is a variable in data and setValue is a method:
methods: {
setValue(value) {
if (this.$store.state.curOrder == "") {
this.$store.commit('setcurDocType', value)
this.curValue = value
} else {
console.log("Debe descartar");
this.$nextTick(() => {this.curValue = this.$store.state.curDocType})
}
}
}
I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});
I am currently building a react app where I need a form with the pre-populated values in it when a user wants to update his/her profile. In the add new user form, It's fine to have the drop-down in its default state. But when a user wants to update profile then there is a project and a group dropdown that needs to have a default value i.e. the value when the user was created. Means the drop-down should be populated with the project and the group associated with it.
<Select
multi={true}
simpleValue
required
value={value}
options={[{value:'One', label:'PROJECTONE'},{value:'Two', label:'PROJECTTWO'}]}
onChange={handleInputChange}
/>
So I need a dropdown with the prepopulated projects i.e. PROJECTONE and PROJECTTWO both.
This is the ScreenShot of my update profile. I have the value which I want to set in the dropdown but if I set comma separated string then when I want to remove that option it is not being affected i.e. I am not able to remove that option.
Question Update 1:
So here is my full component
export interface IInputProps {
required?: boolean;
type: string;
placeholder?: string;
menuItems?: Object[];
isDisabled?: boolean;
onSelect?: (value) => void;
defaultValue?: string;
id?: string;
multi?: boolean;
searchable?: boolean;
}
export interface IInputState {
value: string;
}
export class Input extends React.PureComponent<IInputProps, IInputState> {
constructor({ defaultValue }) {
super();
this.state = { value: (defaultValue || '')};
}
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value) {
this.setState({
value: nextProps.defaultValue
});
}
}
handleInputChange = (selectedValue) => {
this.setState({
value: selectedValue,
});
}
get value() {
return this.state.value;
}
render() {
const { props: { searchable, multi, id, menuItems, required, isDisabled, placeholder, type, defaultValue },
state: { value, dropDownValue }, handleInputChange } = this;
return (
<Select
multi={multi ? multi : false}
simpleValue
searchable={searchable ? searchable : false}
disabled={isDisabled ? isDisabled : false}
required
value={value}
placeholder={placeholder ? placeholder : 'Select...'}
options={menuItems}
onChange={handleInputChange}
/>
);
}
}
}
I am using this input component whereever I need a Select Input and passing the props.
When I use this component as..
<Input
multi={true}
defaultValue="PROJECTONE,PROJECTTWO"
ref="myProjects"
id="myProjects"
menuItems={[{value:'One', label:'PROJECTONE'},{value:'Two', label:'PROJECTTWO'}]}
/>
Then nothing is being set in it's value.
I am using component will recieve props to check if there is any default value passed if it is passed then I am setting the value.
And Once the value is being set on the dropdown I can not remove it using it's cross button that's the main problem here.
There is a problem in your componentWillReceiveProps function:
```
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value) {
this.setState({
value: nextProps.defaultValue
});
}
}
```
This function can be caused by changes to props or state.
You check nextProps.defaultValue !== value in here, once you check an option of the select, it's value may not be equal to the nextProps.defaultValue, then you always set the value state to the defaultValue, so you can not get the right value of the option.
You need also check whether the nextProps.defaultValue and the this.props.defaultValue is same or not, so that you will get the right changed value:
```
componentWillReceiveProps(nextProps) {
if (!nextProps.defaultValue) {
return;
}
const { state: { value } } = this;
if (nextProps.defaultValue !== value && nextProps.defaultValue !== this.props.defaultValue) {
this.setState({
value: nextProps.defaultValue
});
}
}
```