AlpineJS variable is reactive at first set only - alpine.js

I'm working on a Laravel app. I already wrote 4 Alpine data stores with no problem at all.
But for the 5th, I'm getting crazy. My unread variable is initiated as an empty array.
Then when I first set it, the reactivity works. I have a counter showing the length in the variable. As I loop again to get new values on the server side, the unread variable is set again. Its length is increasing but no reactivity in DOM.
Alpine.store('header', {
timer: null,
unread: [],
init: function(event) {
this.timer = window.setInterval(this.refreshNotifications, 10000);
this.refreshNotifications();
},
refreshNotifications: function() {
axios.get('/api/notifications/admin/'+admin.id)
.then(response => {
if (response.hasOwnProperty('data')) {
this.unread = response.data;
}
})
.catch(error => {
console.log(error);
})
},
})
<!-- Notification badge -->
<span
x-show="$store.header.unread.length > 0"
x-text="$store.header.unread.length"
></span>
At first load, counter displays 1. In the background, I add a new notification. Axios gets it and returns it, no problem.
A console.log in the Axios promise shows the right value.
But counter remains at 1.
Couple of lines below, I have a <template x-for ... and it has the same issue. It doesn't update. I may be very tired but I really can't see the trick.
Any help would be appreciated.
Thanks
EDIT : I tried many things including setting a getter method "getCount" or others things.

Related

Vue not updating computed property based on a method, but the same method returns correct value when called directly

I have a simple button component for submitting forms and it should give a bit of feedback when clicked on it: show a spinner and disable the button while the form is being submitted and then also keep it disabled if there are any errors returned from the server until the errors are cleared (errors are cleared by changing the input of fields that have them).
Here's the template of the component:
<template>
<button type="submit" :disabled="disabled">
<loading-spinner v-if="submitting"></loading-spinner>
<slot>Submit</slot>
</button>
</template>
<script>
export default {
props: {
form: {
type: Object,
required: true
}
},
created() {
// Globally available event handler which listens for events
// emitted by Form object - it works fine
Event.$on('submitting', () => this.submitting = true);
Event.$on('submitted', () => this.submitting = false);
},
data() {
return {
submitting: false,
}
},
computed: {
// Here is the problem: this.form.errors.any() doesn't get updated in the
// computed prop even when errors are cleared. This same method, however,
// returns correct result when called directly via a test button
disabled() {
return this.submitting || this.form.errors.any()
},
// doesn't get updated either - apparently
// errors() {
// return this.form.errors.any()
// }
},
}
</script>
The button gets Form object passed to so it can also have access to Errors object which has some convenient methods to manage the errors. The Form object that is passed as prop is reactive inside the submit-button component and its fields get updated in Vue Devtools as I type into the form (on parent component), but the computed prop (disabled or errors) that relies upon it doesn't get updated.
When I submit the form with wrong data, I get errors, so form.errors.any() returns true. However, once errors are returned the first time, the button turns disabled and it "freezes" like that (form.errors.any() keeps returning true even when it isn't).
I know computed props are cached, but by definition they should update when their dependency gets updated (in this case the computed property disabled should get updated because this.form.errors.any() is updated). However, it seems that submit-button component "ignores" the update of this.form.errors.any() in its computed property (it keeps returning true once errors are present and it never gets updated). The same thing happens if I make a same computed property on parent component.
Additional info
When I manually check its value (e.g. via a button that calls form.errors.any()), it logs the correct value to the console every time, but it doesn't get updated in my computed property.
The any() method on Errors object - nothing fancy here
any() {
return Object.keys(this.errors).length > 0;
}
The Form and Errors object both work fine: their methods return normal values when called directly (via a button click, for example) from any component and they dispatch events as expected, they just don't react on a computed prop.
Workaround
The solution for me was to use a method instead of computed property, but it's a bit too verbose and I'd mostly like to know why can't I use more elegant computed property in this case?
<template>
<button type="submit" :disabled="submitting || anyErrors()">
<loading-spinner v-if="submitting"></loading-spinner>
<slot>Submit</slot>
</button>
</template>
<script>
export default {
...
methods: {
anyErrors() {
return this.form.errors.any()
}
}
}
</script>
You need to place the form.errors property on your props for form in order to have it reactive out of the box. Otherwise, when you add errors, you have to use the Vue.set() method in order to make it reactive.
So your props object should look like:
props: {
form: {
type: Object,
required: true,
errors: {},
}
},
More info:
https://v2.vuejs.org/v2/guide/reactivity.html
(edited to make errors an object, as indicated in the question, instead of an array)

cypress.io how to remove items for 'n' times, not predictable, while re-rendering list itself

I've a unpredictable list of rows to delete
I simply want to click each .fa-times icon
The problem is that, after each click, the vue.js app re-render the remaining rows.
I also tried to use .each, but in this cas I got an error because element (the parent element, I think) has been detached from DOM; cypress.io suggest to use a guard to prevent this error but I've no idea of what does it mean
How to
- get a list of icons
- click on first
- survive at app rerender
- click on next
- survive at app rerender
... etch...
?
Before showing one possible solution, I'd like to preface with a recommendation that tests should be predictable. You should create a defined number of items every time so that you don't have to do hacks like these.
You can also read more on conditional testing, here: https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Definition
That being said, maybe you have a valid use case (some fuzz testing perhaps?), so let's go.
What I'm doing in the following example is (1) set up a rendering/removing behavior that does what you describe happens in your app. The actual solution (2) is this: find out how many items you need to remove by querying the DOM and checking the length, and then enqueue that same number of cypress commands that query the DOM every time so that you get a fresh reference to an element.
Caveat: After each remove, I'm waiting for the element (its remove button to be precise) to not exist in DOM before continuing. If your app re-renders the rest of the items separately, after the target item is removed from DOM, you'll need to assert on something else --- such as that a different item (not the one being removed) is removed (detached) from DOM.
describe('test', () => {
it('test', () => {
// -------------------------------------------------------------------------
// (1) Mock rendering/removing logic, just for the purpose of this
// demonstration.
// -------------------------------------------------------------------------
cy.window().then( win => {
let items = ['one', 'two', 'three'];
win.remove = item => {
items = items.filter( _item => _item !== item );
setTimeout(() => {
render();
}, 100 )
};
function render () {
win.document.body.innerHTML = items.map( item => {
return `
<div class="item">
${item}
<button class="remove" onclick="remove('${item}')">Remove</button>
</div>
`;
}).join('');
}
render();
});
// -------------------------------------------------------------------------
// (2) The actual solution
// -------------------------------------------------------------------------
cy.get('.item').then( $elems => {
// using Lodash to invoke the callback N times
Cypress._.times($elems.length, () => {
cy.get('.item:first').find('.remove').click()
// ensure we wait for the element to be actually removed from DOM
// before continuing
.should('not.exist');
});
});
});
});

React/Redux form submission

In my application, I am using actions to do all of my ajax calls. When the results come back, it dispatches them to the reducer, which then puts it in the store. My component is bound to the property and will then be able to get it from the store.
However, I am having an issue trying to figure out the best way to do form submissions. From a listing page, a user can click on a link from any row to open a modal. This modal has a form in it. When the form is filled out, it will then pass the data along to an action, which will submit it. The only response from a valid submission is a HTTP 200.
Without using callbacks, how would the modal know that the ajax call is complete, so it can close itself? As of now, I have a flag in the store called form.processing. This is default to false, and the action will set it to true when it begins and false when its done. The component watches this and then knows when it goes from true to false and knows everything is done. However, I feel like there should be a better way.
Or should I be using callback in forms, even though we don't follow that process for any other ajax call?
Here are following pseudo code can help you:
constructor () {
this.state = {
disaplyModalPopup: false;
}
}
handleSubmit = () => {
this.setState({disaplyModalPopup: true})
let payLoad = { 'Key':this.state.something }
this.props.hitAPI(payLoad).then((res) => {
if (res.data.success) {
this.setState({
'disaplyModalPopup': false
})
}else{
this.setState({
'disaplyModalPopup': true,
'errorMessage': 'something wend wrong'
})
}
})
}
rendor (){
let errorMessage = {this.state.errorMessage}
let disaplyModalPopup = {this.state.disaplyModalPopup}
return (
{disaplyModalPopup ? <modal> </modal> : ''}
{ errorMessage? 'errorMessage': ''}
)
}
Here I have handled your modalPopup with disaplyModalPopup state.
And After get in the response saved in reducer, it is changes as {disaplyModalPopup: false}
And modalPopUp HTML will disappear.
But in case your API get in failed while making response.
So that case: i have handle as error message in as text
errorMessage where you can show your error message. So that Modal is closed side by side.

Protractor does not perceive a quick change

This is my protractor test:
it("should check email validity", function(){
var resetButton = element(by.id('reset-button'));
element(by.model('Contact.email')).sendKeys('nick');
element.all(by.css('.form-control-error')).each(function (elem, index) {
if (index===1) {
expect(elem.isPresent()).toBe(true);
element(by.model('Contact.email')).sendKeys('#gmail.com').then(
function(){
expect(elem.isPresent()).toBe(false);
}
)
}
});
});
Behind that code there is a form with some input texts. The second one includes the email.form-control-erroris an error message which appears whenever the email format is not correct. The first time expect(elem.isPresent()).toBe(true);passes the test, the second time it does not, even if the error message disappears from the UI. It seems that Protractor does not perceive the fast change; however, it should because it is inside a promise. Do you have any explanation for that?
You should make things more reliable by adding a wait for the element to become not present ("stale") after sending the keys:
element(by.model('Contact.email')).sendKeys('#gmail.com');
var EC = protractor.ExpectedConditions;
browser.wait(EC.stalenessOf(elem), 5000);
expect(elem.isPresent()).toBe(false);

customize addNotice message

I am using addNotice to display any message on screen. Now, I want to customize it and it should be removed after some time(let's say after 10 seconds) like we can do with javascript.
Is this possible to do this using default addNotice message of magento ?
Any Help would be appreciated.
add this script in your page
This will hide the div after 1 second (1000 milliseconds).
$(function() {
setTimeout(function() {
$('.messages').fadeOut('fast');
}, 1000); // <-- time in milliseconds
});
If you just want to hide without fading, use hide().
hope this will help you
Add this to your footer:
setTimeout(function(){
var messages = $$('.messages')[0];
if (messages){
$(messages).hide();
}
}, 10000)
The code above is the prototype version.
If you have jquery already in your website use what #magExp wrote. It's cleaner.
Let say your success message id is "success-msg", then write jquery like
$(function() {
// setTimeout() function will be fired after page is loaded
// it will wait for 5 sec. and then will fire
// $("#success-msg").hide() function
setTimeout(function() {
$("#success-msg").hide('blind', {}, 1000)
}, 5000);
});
Remember that you need to load jQuery Library..

Resources