I have no idea if what I'm doing is correct or not, but here's a simplified version of what I'm trying to do:
I want to have 3 file inputs, with the 2nd and 3rd disabled until the 1st one has had a file selected.
I've tried to do is set the Vuex state variable to whatever the first file input is has selected, but upon doing that the other 2 inputs don't update their disabled state.
I have some file inputs that are created dynamically, like so:
Vue.component('file-input', {
props: ['items'],
template: `<div><input type="file" v-on:change="fileSelect(item)" v-bind:id="item.id" v-bind:disabled="disabledState"></div>`,
methods: {
fileSelect: function(item) {
store.commit('fileSelect', file);
}
},
computed: {
disabledState: function (item) {
return {
disabled: item.dependsOn && store.getters.getStateValue(item.dependsOn)
}
}
}
}
The data for the component is from the instance:
var vm = new Vue({
data: {
items: [
{ text: "One", id: "selectOne" },
{ text: "Two", id: "selectTwo", dependsOn: "fileOne" },
{ text: "Three", id: "selectThree", dependsOn: "fileOne" }
}
});
Now, notice the "dependsOn". In the Vuex store, I have a corresponding state item:
const store = new Vuex.Store({
state: {
files: [
{
fileOne: null
}
]
},
mutations: {
fileSelect(state, file) {
state.files.fileOne = file;
}
},
getters: {
getStateValue: (state) => (stateObject) => {
return state.files.findIndex(x => x[stateObject] === null) === 0 ? true : false;
}
}
});
Now, the above works when everything is first initialized. But once the first input has something selected, the other two inputs don't change.
I'm not sure how to update the bindings once a mutation of the state occurs.
I think you need to refactor your mutation to make the state property mutable, like this:
fileSelect(state, file) {
Vue.set(state.files[0].fileOne, file);
}
Well, I figured it out...
Because my state object is an array of objects, I can't just change one of the property's values with state.files.fileOne. I needed to do state.files[0].fileOne.
Related
I am trying to update an object in redux using spread operator but I am not being able to.
Initial state is an empty object because category is received dynamically from api call.
pages and data are both objects which i want to update using spread operator (or whatever works best)
state = {
[category]: {
pages: {
key: value(array)
},
data: {
key: value(array)
}
}
}
At my reducer I try to update it like this
return {
...state,
[category]: {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
},
};
but i get "error: TypeError: Cannot read property 'pages' of undefined"
What am I doing wrong and how can I update them correctly?
Because state.category is undefined when you fetch this category for the first time. You can fix it like that:
return {
...state,
[category]: state.category
? {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
}
: {
pages,
data: doctors,
total,
},
};
I got a react component with a form. I keep the form settings in an object outside the component:
const initialForm = {
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
}
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(initialForm);
...
}
the elementAtts is the attributes I pass the input.
What I'm trying to do is open a modal which displays the form - one time for display only and one time with editing allowed - can be for editing an existing item or for adding a new item.
I'm doing it like this for editing an existing item and for displaying:
//a callback
const OpenModalForEditOrDisplay = (isEditable, cardObject) =>
{
setFormSettings(prevForm =>
{
let newForm = {...prevForm};
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
return {...newForm}
});
setIsFormOpen(true);
}
};
and for adding a new item:
setFormSettings(initialForm);
setIsEditing(true);
setIsFormOpen(true); //this is merely a state saying if to show the modal with the form
The user can then submit or cancel the form, and on either case I'm doing:
setFormSettings(initialForm);
The problem is that it seems like initialForm is overridden and if I open the form for display only, it stays on display when trying to open the form for addition because the code for the edit part changed what I thought would be a copy of the initialForm. If I remove these lines in the open for edit function the form stays with the initial form's settings:
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
Why is the initial form being overridden here?
You have used Spread syntax to clone the prevForm values within setFormSettings. However you must note that Spread syntax only shallow clones the object and does not perform a deep cloning which means that you nested values within the prevForm still hold the original reference and when you update the values like
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
You are mutating it at the original reference. The correct way to update state is to immutably update it by cloning each nested level like
setFormSettings(prevForm =>
{
let newForm = {
...prevForm,
name: {
...prevForm.name,
elementAttrs: {
...prevForm.name.elementAttrs,
readOnly: !isEditable,
}
}
description: {
...prevForm.description,
elementAttrs: {
...prevForm.description.elementAttrs,
readOnly: !isEditable,
}
}
};
return newForm;
});
This is a problem for Deep Copy and shallow copy. The 'formSettings' data source is 'initialForm'. Use 'setFormSettings' will to change 'initialForm' , this is right. Because you use a shallow copy when you initialize it. you can use a function Deep Copy to 'initialForm'.
const createInitialForm = () => ({
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
})
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(createInitialForm());
...
}
I am doing some filtering using React Context and I am having some difficulty in updating a child's array value when a filter is selected.
I want to be able to filter by a minimum price, which is selected in a dropdown by the user, I then dispatch an action to store that in the reducers state, however, when I try and update an inner array (homes: []) that lives inside the developments array (which is populated with data on load), I seem to wipe out the existing data which was outside the inner array?
In a nutshell, I need to be able to maintain the existing developments array, and filter out by price within the homes array, I have provided a copy of my example code before, please let me know if I have explained this well enough!
export const initialState = {
priceRange: {
min: null
},
developments: []
};
// Once populated on load, the developments array (in the initialState object)
// will have a structure like this,
// I want to be able to filter the developments by price which is found below
developments: [
name: 'Foo',
location: 'Bar',
distance: 'xxx miles',
homes: [
{
name: 'Foo',
price: 100000
},
{
name: 'Bar',
price: 200000
}
]
]
case 'MIN_PRICE':
return {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: [
...state.developments.map(development => {
// Something here is causing it to break I believe?
development.homes.filter(house => house.price < action.payload);
})
]
};
<Select onChange={event=>
dropdownContext.dispatch({ type: 'MIN_PRICE' payload: event.value }) } />
You have to separate homes from the other properties, then you can apply the filter and rebuild a development object:
return = {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: state.developments.map(({homes, ...other}) => {
return {
...other,
homes: homes.filter(house => house.price < action.payload)
}
})
}
I trying to return back sorted data (which is the already defined state) in a list with the help of a getter, then assign it to the html list in my vue, but it seems it's empty when I check with the vuex tools.
I don't know what am doing wrong.
Below is my store.js file
export default {
namespaced: true,
state:{
displayChatMessages: [],
},
mutations:{
create(state, payload) {
state.displayChatMessages.push(payload)
},
reset(state){
state.displayChatMessages = []
},
},
actions :{
getAllData:({commit}, payload) => {
commit('create',payload)
},
},
getters:{
filteredChatMessages: state => (chatID) => {
return state.displayChatMessages[0]
.filter(el => el.groupid === chatID).sort((l,r)=> l.timestamp - r.timestamp)
},
},
}
Then, after, I call it in the computed area like below :
...mapGetters('chatMessages',['filteredChatMessages']),
Then , I call the Getter inside my function , like below :
getFilteredMessages: function() {
let vm = this
return vm.filteredChatMessages(vm.groupID)
},
Then afterwards, then I set the getFilteredMessages() to the list , getFilteredMessages() , is also defined in the computed section.
But when I look into my vuex tools , I don't see it as an array :
What am I doing wrong ?
I have a dynamic component being injected in the slot of another one and I pass the props object to it. But when I update the data (an array) which has been assosiated with the prop (dataT: this.tableData), that prop isn't being updated inside the component.
It seems like I have a deal with two different objects but the array was passed by the reference, wasn't it?
This is the main component
<template>
<Button #click="addWindows"></Button>
<Window v-for="window in windows" :key="window.id">
<component :is="window.name" v-bind="window.props" #onDeleteRow="handleDeleteRow"></component>
</Window>
</template>
<script>
export default{
data(){
return{
windows:[],
tableData:[
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
methods:{
addWindows(){
this.windows = [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
},
{
id: 1,
name: 'Component2',
props: {}
}];
},
handleDeleteRow(id){
this.tableData = this.tableData.filter(r => r.id != id);
}
}
}
</script>
I expect updating dataT prop in Component1 when I modify this.tableData in the main component.
Original answer based on an earlier version of the question
If you make windows a computed property it can depend on tableData:
export default {
data() {
return {
tableData: [
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
computed: {
windows () {
return [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
}, {
id: 1,
name: 'Component2',
props: {}
}
]
}
}
}
If you can't make all of it a computed property, e.g. because you need to be able to modify it, then keep it as data and just use the computed property to create the array needed in your template. In that case the computed property would just be merging together different parts of the data into the correct form.
In your original code, the line dataT: this.tableData won't work because this.tableData doesn't exist yet, it'll just be undefined. There's no lazy evaluation here, it needs to resolve to the correct object at the point it hits that line.
Even if it was able to get access to the correct object it wouldn't help because in handleDeleteRow you're reassigning tableData to point to a different object. Passing 'by reference' has nothing to do with the name you use to identify the object, it refers to a reference in memory.
Incidentally, v-on also supports an object syntax, just like v-bind, so you could make onDeleteRow optional in a similar fashion.
Update based on the edited question
When you write this in addWindows:
props: {
dataT: this.tableData
}
This will assign the current value of this.tableData to dataT. That current value will be an array and as arrays are reference types any modifications made to that array will apply no matter what identifier is used to reference it.
However, this line...
this.tableData = this.tableData.filter(r => r.id != id);
... does not modify that array. Instead it assigns a totally new array to this.tableData. This will have no effect on the array referenced by dataT, which is unchanged.
There are several ways you could approach solving this, including using a computed property. However, a property getter might provide a convenient sleight-of-hand:
addWindows () {
const vm = this;
this.windows = [
{
id: 0,
name: 'Component1',
props: {
get dataT () {
return vm.tableData
}
}
},
{
id: 1,
name: 'Component2',
props: {}
}
];
}
This will always evaluate to the current value of tableData. Vue's reactivity should be fine with this extra indirection, it just sees it as equivalent to accessing tableData directly.
TL;DR
The issue is with your binding. Use the following:
<component
:is="window.name"
:dataT="window.props.dataT"
#onDeleteRow="handleDeleteRow">
</component>
Explanation
the v-bind attribute specifies what prop is bound to what value (or reference). In your case, you didn't specify what values you're binding to what props, thus the component props weren't bound as expected.