Vue component changing its own property in store - javascript

okay, it's a little hard to explain, but i'll try.
i have some components rendered in a loop from an array of cards in my vuex store
<card-base
v-for="(card, i) in cards"
:class="`dashboard-card ${card.hidden ? 'hidden' : ''}`">
note the class depending on card.hidden, which is false onload
now, every <card-base> has a button (hide), which is supposed to.. well, hide it.
the way i try to do that is:
a v-btn in the card-base component gets a #clickproperty, which calls a method.
The Problem / Question
now i want to set the hidden property of the clicked card to true.
of course
minimizeDashboardCard() {
console.log(this.hidden)
this.hidden = true
}
doesn't work, because this is not the actual object from vuex, which provides the reactive properties, but just the "element".
if i set this.hidden = true, nothing changes (except the console.log correctly showing "true" after the first click)
but how can i access the actual vuex object from this? how do i get the index which i want to edit in my array? is there a way to have the card component "say" something like:
"dispatch an action that changes ME in the actual vuex array"?
like dispatch('hideCard', this) and have it actually working?
TLDR
how can i find out the index of the clicked card in the array, or directly target it in any other way? is there a connection between a rendered element and the array in store which defines it?
i hope you understood my problem :D
thanks!

Related

Vue if condition is met but doesnt show result

I got a v-if and if the Condition is true it should show some other vue elements
<template v-if="$store.state.Zell_Open[$store.state.PAGENUMBER]===true">
my state:
Zell_Open: [false],
PAGENUMBER: 0
When i change Zell_Open to true the component that should be shown is not shown, it will only be shown when i manually change the PAGENUMBER state (via Vue Dev tools) one up and one down, then the component that should be shown is shown.
I tried to use this as a work around and tryied to just change the PAGENUMBER state one up and one down when the Zell_open state is udated but this doesnt work too, it only works when using dev tools.
I also tried using v-show in a v-div instead of a v.if but that also just resulted in the same problem.
EDIT:
I tried using computed variables and i tried using getters instead of directly using the state but the i still got the same problem
To clarify:
It worked when i just used
<template v-if="$store.state.Zell_Open===true">
That was before the Zell_Open state was an array, there it worked.
So i think the [$store.state.PAGENUMBER] is causing the problem.
Try Vue.set in your store mutations:
https://v2.vuejs.org/v2/api/#Vue-set
And definitely move " $store.state.Zell_Open[$store.state.PAGENUMBER] === true " into a computed property. This way your template will remain clean of any logic.
You either have to make a getter in your Vuex store, then a computed property on the component and watch it like Vue data, or you can use a Vuex watcher to react on the changes. Vuex provides a watcher method Vuex watcher method like Vue's that you can use to watch for changes.
this.$store.watch(
(state)=>{
return this.$store.getters.your_getter
},
(val)=>{
//something changed do something
},
{
deep:true // when you have to watch arrays or objects
}
);

Issue with update timing for a state in React Component (2 clicks needed instead of 1)

I’m working on a solo project using React and I’ve been stuck on something for the past 2 days…I'm starting and I'm very beginner so it's maybe something very basic, but I'm struggling...
To try to be concise and clear:
I have a searchBar component, that searches through a local database, and returns objects associated with the search keyword. Nothing complicated so far.
Each rendered object has a button that triggers a function onClick. The said function is defined in my App component and is as follow:
changeState(term){
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{idToRender[0].recoInfo.push(response)})
})
this.setState({
objectToRender: idToRender
});
to explain the above code, what it does is that first, it identifies which object’s button has been clicked on, and send said object to a variable called idToRender. Then, it takes the reco state of that object, and store it to another variable called recoToFind. Then it calls the map() method on recoToFind, make an API request (the discogs() method) for each element of the recoToFind array and push() the results into the recoInfo state of idToRender. So by the end of the function, idToRender is supposed to look like this:
[{
…
…
recoInfo: [{1stAPI call result},{2ndAPI call result}…]
}],
The array contains 1 object having all the states of the object that was originally clicked on, plus a state recoInfo equal to an array made of the results of the several API calls.
Finally, it updates the component’s state objectToRender to idToRender.
And here my problem is, onClick, I do get all the states values of the clicked on object that get rendered on screen (as expected with how I coded the nested components), BUT, the values of the recoInfo are not displayed as expected (The component who’s supposed to render those values is nested in the component rendering the clicked on object other states values). However, they get displayed properly after a SECOND click on the button. So it seems my problem boils down to an state update timing trouble, but I’m puzzled, because this function is calling setState once and I know for a fact that the state is updated because when I click on the button, the clicked on Object details get displayed, but somehow only the recoInfo state seems to not be available yet, but only becomes available on a second click…
Would anyone have a way to solve this issue? :(
It somehow feels like my salvation lies in async/await, but I’m not sure I understand them correctly…
thanks very much in advance for any help!
Is this someting you want to do?
changeState(term) {
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{
idToRender[0].recoInfo.push(response)
this.setState({
objectToRender: idToRender
});
})
})
}
you can call setState once async call is done and result received.

Access $refs from other components not in the same level as current component

I'm working on a Vue application.
It has a header and then the main content.
Nesting and structure as below
TheHeader.vue -> TheLogin.vue
MainContent.vue -> ShoppingCart.vue -> OrderSummary.vue
I need to access an element in TheLogin.vue from OrderSummary.vue
this.$refs.loginPopover.$emit('open')
gives me an error "Cannot read property '$emit' of undefined" so obviously I am not able to access $refs from other components.
The question is how do I get hold of refs from other components?
Thanks in advance!
Edit 1 - Found out $refs works with only child components.
How do I access elements across components in different level?
You definitely don't want to be reaching through the hierarchy like that. You are breaking encapsulation. You want a global event bus.
And here's a secret: there's one built in, called $root. Have your OrderSummary do
this.$root.emit('openPopup');
and set up a listener in your TheLogin's created hook:
this.$root.on('openPopup', () => this.$emit('open'));
In general, you should try to avoid using refs.
For anyone who comes here later and wants to access $refs in parent component, not in this particular case for emitting events since event bus or a store would suffice but let's just say you want to access some element in parent to get it's attributes like clientHeight, classList etc. then you could access them like:
this.$parent.$parent.$refs //you can traverse through multiple levels like this to access $ref property at the required level
You can put a function like this on your component to do this. I put mine in a Mixin:
public findRefByName(refName) {
let obj = this
while (obj) {
if (obj.$refs[refName]) {
return obj.$refs[refName]
}
obj = obj.$parent
}
return undefined
}
I also added some accessors to help:
get mycomponent() {
return this.findRefByName('mycomponent')
}
And once that exists, you can access your component by simply doing:
this.mycomponent
Thanks for that tip Abdullah!
In my case I was looking for a sibling, so in case someone comes looking for that, here's an example:
var RefName='MyCoolReferenceName';
var MyRef,x;
for(x=0;x<this.$parent.$children.length;x++)
{
if(typeof this.$parent.$children[x].$refs[RefName] !='undefined')
MyRef=this.$parent.$children[x].$refs['LABEL_'+bGroupReady.ChildID];
}
if(typeof MyRef !='undefined')
MyRef.error=true;
PS - The reason I'm doing MyRef.error=true is because I was having ZERO luck with Quasar inputs and lazy-rules="ondemand". Turns out you can just set .error=true to activate the error message and the red highlighting and .clearValidation() event to clear it back out. In case someone is trying to do that as well!

ReactJs: change state in response to state change

I've got a React component with an input, and an optional "advanced input":
[ basic ]
Hide Advanced...
[ advanced ]
The advanced on the bottom goes away if you click "Hide Advanced", which changes to "Show Advanced". That's straightforward and working fine, there's a showAdvanced key in the state that controls the text and whether the advanced input is rendered.
External JS code, however, might change the value of advanced, in which case I want to show the [advanced] input if it's currently hidden and the value is different than the default. The user should be able to click "Hide Advanced" to close it again, however.
So, someone external calls cmp.setState({advanced: "20"}), and I want to then show advanced; The most straightforward thing to do would just be to update showAdvanced in my state. However, there doesn't seem to be a way to update some state in response to other state changes in React. I can think of a number of workarounds with slightly different behavior, but I really want to have this specific behavior.
Should I move showAdvanced to props, would that make sense? Can you change props in response to state changes? Thanks.
Okay first up, you mention that a third party outside of your component might call cmp.setState()? This is a huge react no-no. A component should only ever call it's own setState function - nothing outside should access it.
Also another thing to remember is that if you're trying change state again in response to a state change - that means you're doing something wrong.
When you build things in this way it makes your problem much harder than it needs to be. The reason being that if you accept that nothing external can set the state of your component - then basically the only option you have is to allow external things to update your component's props - and then react to them inside your component. This simplifies the problem.
So for example you should look at having whatever external things that used to be calling cmp.setState() instead call React.renderComponent on your component again, giving a new prop or prop value, such as showAdvanced set to true. Your component can then react to this in componentWillReceiveProps and set it's state accordingly. Here's an example bit of code:
var MyComponent = React.createClass({
getInitialState: function() {
return {
showAdvanced: this.props.showAdvanced || false
}
},
componentWillReceiveProps: function(nextProps) {
if (typeof nextProps.showAdvanced === 'boolean') {
this.setState({
showAdvanced: nextProps.showAdvanced
})
}
},
toggleAdvancedClickHandler: function(e) {
this.setState({
showAdvanced: !this.state.showAdvanced
})
},
render: function() {
return (
<div>
<div>Basic stuff</div>
<div>
<button onClick={this.toggleAdvancedClickHandler}>
{(this.state.showAdvanced ? 'Hide' : 'Show') + ' Advanced'}
</button>
</div>
<div style={{display: this.state.showAdvanced ? 'block' : 'none'}}>
Advanced Stuff
</div>
</div>
);
}
});
So the first time you call React.renderComponent(MyComponent({}), elem) the component will mount and the advanced div will be hidden. If you click on the button inside the component, it will toggle and show. If you need to force the component to show the advanced div from outside the component simply call render again like so: React.renderComponent(MyComponent({showAdvanced: true}), elem) and it will show it, regardless of internal state. Likewise if you wanted to hide it from outside, simply call it with showAdvanced: false.
Added bonus to the above code example is that calling setState inside of componentWillReceiveProps does not cause another render cycle, as it catches and changes the state BEFORE render is called. Have a look at the docs here for more info: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Don't forget that calling renderComponent again on an already mounted component doesn't mount it again, it just tells react to update the component's props and react will then make the changes, run the lifecycle and render functions of the component and do it's dom diffing magic.
Revised answer in comment below.
My initial wrong answer:
The lifecycle function componentWillUpdate will be ran when new state or props are received. You can find documentation on it here: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
If, when the external setState is called, you then set showAdvanced to true in componentWillUpdate, you should get the desired result.
EDIT: Another option would be to have the external call to setState include showAdvanced: true in its new state.

Reset Ember Component on Load

I have an Ember.Component that adds items to an empty array and returns the array on submission. The problem is, if I navigate away from the Route that contains the Component (both after submitting and without submitting), and then go back to it later, the information that was last in the array is still there. I would like to to be reset every time I navigate to the route with the component.
If this were a route, I'd simply write a willTransition or deactivate method to reset my attributes. But since it's a component, it doesn't have those methods, and I can't (that I know of) access the attribute I wish to reset from the parent route. So, how can I reset this array to be empty (or reset the the entire component) every time I load this route? Thanks!
More likely than not, you're not setting the value you're using properly. Take these examples:
Ember.Component.extend({
items: []
});
Ember.Component.extend({
items: null,
init: function() {
this._super();
this.set('items', []);
}
});
In the first component, the same items array is shared by every instance of the component. So if you add an item, then create a new component, the new component still has the item (which I think is your problem).
In the second component, you can see that I set the items property in the init function. And when I set the property, I set it to a different array every time. Now, each component has their own items property.
It's hard to say without your code, but this seems like your issue.

Categories

Resources