I have built a component that composed of an array(selectedItems) and a boolean (toggle).
the component has items array.
based on the current data both hold I created a computed property:
computed:{
isValid(){
const {toggle, selectedItems} = this
return toggle && selectedItems.length || !toggle && selectedItems.length < items.length
}
}
my model is an object that has all 3: {selectedItems, isValid, toggle}
I wrote a function to update the model (which is named output):
updateModel(key, value) {
this.$emit('update', { ...this.output, [key]: value })
}
I'm calling this function when updating toggle or selectedItems, but I want it to be up to date with the validity property as well.
when I call that function in the computed it enters infinite loop of events for some reason, which I couldn't figure out.
Then I tried a different approach,
I created a watch on the computed isValid property and in the handler I call updateModel (the same way I tried to call it before, and that works.
Why calling it in the computed function causing it to some to be invoked again and again?
Thanks.
Related
I am trying to change a variable in react with a callback function but cannot seem to do so. Here is my react component:
const MyComponent = () => {
let scenePinned;
const sceneCallback = event => {
if (event && event.state === 'DURING') {
console.log('Pinned');
scenePinned = true;
} else {
console.log('Not Pinned');
scenePinned = false;
}
};
console.log(scenePinned);
return (
<div>
<div style={scenePinned ? 'pinned' : 'not pinned'}/>
{(progress, event) => (
//Stuff Happens Here
), sceneCallback(event) )}
</div>
);
}
I am using react-scrollmagic and am trying to get the scenePinned variable to change from false to true and back to false again when scene is pinned to top. The console logging of Pinned and Not Pinned is happening correctly but I cannot seem to change the scenePinned variable. I am sure this is something very basic that I am not getting but I cannot understand why this is happening. Any help would be appreciated.
Note: I have tried using state to store the value but the callback is fired on scroll so the maximum depth is exceeded when trying to use state to store the scrolling status.
You need to use state for this. Otherwise the variable is reinitialized every time the component is rendered, and the value is lost.
console.log(scenePinned);
will run for the first time when the page loads
with react we use state the handle dynamic values.
or use rxjs
or create your own object and set listeners on it. with some custom event
so ex. with state
state={scenePinned:null}
then inside render method console.log(this.state.scenePinned)
A possible solution is to define a state variable in a parent component that will pass it to <MyComponent> as a prop.
Them move the sceneCallback function to the parent component and pass it as a prop to <MyComponent>
An explanation on how to define such a callback exists in many places. Here is one: (mine... ;) https://stackoverflow.com/a/55555578/5532513
I am having an issue with watchers not being triggered upon data change in my component. The property in the data is reactive, as it has been set upon component creation and not later on.
Here is the piece of code, in which the issue is:
https://codesandbox.io/s/nlpvz0y6m
To explain in more detail, the status property gets its status from the parent, which intern gets it from a Vuex state object, it is being passed to the component successfully, as I am able to log it and change it.
However, when I setup a watcher, to execute a function upon a change in it's value, it simply doesn't trigger. Regardless how I make the change - whether with an internal method of the component or an event.
What I need is for the watcher to trigger upon change of the status property, but am not certain why it does not reflect it at all.
The structure is as it follows: BottomBar is the parent, a bool value is passed as property to Spin.vue as a prop and then the prop is assigned to a data property on the child component.
The bool value itself, comes from index.js, where the Vuex instance is.
In the console, it is showing the following two errors
[vuex] unknown getter: isSpinning
[vuex] unknown mutation type: spinIt
The issue seems to be how the store is set up. Try this.
export const store = new Vuex.Store({
state: {
controls: {
spin: false
}
},
getters: {
isSpinning: state => {
return state.controls.spin;
}
},
mutations: {
spinIt(state) {
return (state.controls.spin = !state.controls.spin);
}
}
});
You had your mutations and getters sat inside your state. I have moved them outside, and updated the references inside to make the code work as expected.
https://codesandbox.io/s/8xyxmvr8jj
So I have a Vue page where I'm looping through a few items and I have the following code:
<div v-if="behavior !== 'selected' && filterItems.length >= 5"></div>
<div v-for="(itemName, index) in filterItems" :key="index">[stuff]</div>
Basically I'm looping through some items from an API, but I also want to conditionally show an element if there's 5 or more of those items, otherwise I want it hidden. That element needs to exist outside the loop, but it needs to check how many items are in the loop.
Interestingly enough, the above code works, but it also throws console errors, presumably because I'm accessing "filterItems" outside of the loop itself.
(here's the console error: [Vue warn]: Error in render: "TypeError: Cannot read property 'length' of undefined")
Any ideas on how I can avoid throwing these errors and accomplish what I need in as vue-native a way as possible?
As requested, here's the code declaring filterItems. It's just a prop declared as an array:
props: {
filterItems: Array,
behavior: String,
},
It's being passed in from a parent component as an array.
UPDATE: A POSSIBLE SOLUTION?
So I don't know if this is the best way to do this or not, but I was able to get the desired result by doing the following. I'd love to hear feedback on if this is a satisfactory solution:
I added a data value:
data() {
return {
displaySearch: false,
};
},
Then added:
updated() {
if (this.behavior !== 'selected' && this.filterItems.length >= 5) {
this.displaySearch = true;
}
},
Then ran a check against the new boolean: v-if="displaySearch"
My thinking is that the check run against displaySearch after the page renders and it avoids the TypeError. I tried mounting it, intially and it broke immediately.
Final Solution
See the answer below from Stephen Thomas. I settled on this as it appears to me to be the simplest and most elegant answer.
presumably because I'm accessing "filterItems" outside of the loop itself.
Nope. filterItems is not defined by the v-for loop. It's defined (or it should be defined) in the component's props object. As such, it's completely accessible anywhere in your template.
TypeError: Cannot read property 'length' of undefined"
That indicates that filterItems isn't defined. The parent that includes this component isn't providing a value. You can define a default (empty) array for the property:
props: {
filterItems: {
type: Array,
default() {
return [];
}
},
behavior: String
}
or you can fix the parent
I think it starts the filterItems as an empty array already solves, for example:
date () {
return {
filterItems: []
}
}
Vue doesn't allow declared properties to be accessed outside v-for (to avoid clashes between properties)
you can declare a global boolean variable to show or hide the component? or inject a property into your objects to hide or show
I am new to VueJs and I am doubtful about passing the optional payload
Could anyone please tell me how to pass the value returned by a computed function in the child component to the parent component using the optional payload.
I want to implement a separate independent search component which returns the search results to all other components. The computed function looks like this:
get computedSports () {
if (!this.searchModel || this.searchModel.length === 0)
return this.sports
else
return this.fuseSearch.search(this.searchModel)
}
This is how I am trying to pass the value returned by computed function to its parent component in the child template:
#input="$bus.$emit('computed-sports', computedSports)"
In the parent component, this is how I am trying to access the value of the child's computed function:
v-on:computed-sports=""
I am not quite sure how to access the value here. Could anyone help me out in this?
Thanks!
The argument to a v-on should be a method or inline function that takes the payload as an argument. For example
v-on:computed-sports="handleComputedSports"
and your method might be defined
handleComputedSports(theValue) {
console.log("The value is", theValue);
}
There's an example in this section of the documentation. Your emit is fine. The fact that the value comes from a computed makes no difference to anything.
I must be missing something obvious here. I have a to-do list app which uses a function for creating new lists. Once createList is called I want to then highlight the list by setting its selected prop to true So below are the two methods for doing this. I'm trying to call one after the other. Both of them modify state using the appropriate callback that uses prevState, yet for whatever reason createList does not set the new list in state before toggleSelected gets called, and so listName is undefined in toggleSelected. Is there anyway to ensure the new list object is set in state before calling toggleSelected? I should probably be using Redux but I didn't want to get into it for my first React app.
createList = (listName) => {
const lists = {...this.state.lists};
lists[listName] = {
listName: listName,
selected: false,
todos: {}
};
this.setState(prevState => {
return {lists: prevState.lists};
});
};
toggleSelected = (listName) => {
let selected = this.state.lists[listName].selected;
selected = !selected;
this.setState(prevState => {
return {
bookLists: update(prevState.lists, {[listName]: {selected: {$set: selected}}})
};
});
};
Both methods are called in another component like so after an onSubmit handler with the new list name being passed in:
this.props.createList(newListName);
this.props.toggleSelected(newListName);
PS - If you're wondering what's up with update(), it's from an immutability-helper plugin that allows for easily setting nested values in a state object(in this case, state.lists[listName].selected)--another reason I probably should have gone with Redux.
PPS - I realize I can just set the new list's selected prop to true from the start in creatList but there's more to the app and I need to set it after creation.
Don't do what you're doing in toggleSelected right now, instead toggle the selected flag in your list (without extracting it) and then let your component know you updated the lists data by rebinding the resulting object:
class YourComponent {
...
toggleSelected(listName) {
let lists = this.state.lists;
let list = lists[listName];
list.selected = !list.selected;
this.setState({ lists });
}
..
}
Then make sure that in your render function, where you create the UI for each list, you check whether selected is true or false so you can set the appropriate classNames string.
(Also note that in your code, you used selected = !selected. That isn't going to do much, because you extracted a boolean value, flipped it, and then didn't save it back to where it can be consulted by other code)
The problem is not in the second setState function. It is at the first line of the toggleSelected() method.
When the toggleSelected() method is executed, the first setState haven't been executed.
The flow of the your code is:
createList();
toggleSelected();
setState() in createList();
setState() in toggleSelected();
Solution 1:
Use await and async keywords
Solution 2:
Use redux