Svelte reactive statement not triggering, when variable updated asynchronously in another file - asynchronous

A reactive statement isn't triggering how I expect, in this toy example. I'm updating a variable x in a separate file, and I'd expect this update to trigger a reactive statement in my main app, but it doesn't. When I use the same logic inside my main app, for variable y, it does trigger the reactive statement. Why isn't the reactivity working the same for x as for y?
TestApp.svelte:
<script>
import { x } from './test.mjs';
let y = "unset";
async function inity() {
await new Promise(r => setTimeout(r, 100));
console.log("inity timeout done");
y = "set";
}
inity();
$: console.log("x:", x);
$: console.log("y:", y);
</script>
<p>Hello world</p>>
test.mjs:
export let x = "unset";
async function initx() {
await new Promise(r => setTimeout(r, 100));
console.log("initx timeout done");
x = "set";
}
initx();
The output I see at the console:
x: unset
y: unset
initx timeout done
inity timeout done
y: set

Reactivity is only analyzed within Svelte files. If you update values outside you have to use stores.
The Svelte code is compiled to include the necessary logic to call $: code on change, the JS is not touched and hence will not have any of the necessary logic. Stores have a built-in mechanism for signaling changes that Svelte can work with.

Related

Test an async PipeTransform

Context
I have a basic PipeTransform, expect the fact that it is async. Why? because I have my own i18n service (because of parsing, pluralization and other constraints, I did my own) and it returns a Promise<string>:
#Pipe({
name: "i18n",
pure: false
})
export class I18nPipe implements PipeTransform {
private done = false;
constructor(private i18n:I18n) {
}
value:string;
transform(value:string, args:I18nPipeArgs):string {
if(this.done){
return this.value;
}
if (args.plural) {
this.i18n.getPlural(args.key, args.plural, value, args.variables, args.domain).then((res) => {
this.value = res;
this.done = true;
});
}
this.i18n.get(args.key, value, args.variables, args.domain).then((res) => {
this.done = true;
this.value = res;
});
return this.value;
}
}
This pipe works well, because the only delayed call is the very first one (the I18nService uses lazy loading, it loads JSON data only if the key is not found, so basically, the first call will be delayed, the other ones are instant but still async).
Problem
I can't figure out how to test this pipe using Jasmine, since it is working inside a component I know it works, but the goal here is to get this fully tested using jasmine, this way I can add it to a CI routine.
The above test:
describe("Pipe test", () => {
it("can call I18n.get.", async(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
expect(pipe.transform("nope", {key: 'test', domain: 'test domain'})).toBe("test value");
})));
});
Fails because since the result given by the I18nService is async, the returned value is undefined in a sync logic.
I18n Pipe test can call I18n.get. FAILED
Expected undefined to be 'test value'.
EDIT: One way to do it would be to use setTimeout but I want a prettier solution, to avoid adding setTimeout(myAssertion, 100) everywhere.
Use fakeAsync from #angular/core/testing. It allows you to call tick(), which will wait for all currently queued asynchronous tasks to complete before continuing. This gives the illusion of the actions being synchronous. Right after the call to tick() we can write our expectations.
import { fakeAsync, tick } from '#angular/core/testing';
it("can call I18n.get.", fakeAsync(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
let result = pipe.transform("nope", {key: 'test', domain: 'test domain'});
tick();
expect(result).toBe("test value");
})));
So when should we use fakeAsync and when should we use async? This is the rule of thumb that I go by (most of the time). When we are making asynchronous calls inside the test, this is when we should use async. async allows to test to continue until all asynchronous calls are complete. For example
it('..', async(() => {
let service = new Servce();
service.doSomething().then(result => {
expect(result).toBe('hello');
});
});
In a non async test, the expectation would never occur, as the test would complete before the asynchronous resolution of the promise. With the call to async, the test gets wrapped in a zone, which keeps track of all asynchronous tasks, and waits for them to complete.
Use fakeAsync when the asynchronous behavior is outside the control of the test (like in your case is going on in the pipe). Here we can force/wait for it to complete with the call to tick(). tick can also be passed a millisecond delay to allow more time to pass if needed.
Another option is to mock the service and make it synchronous, as mentioned in this post. When unit testing, if your components in test are dependent on heavy logic in the service, then the component in test is at the mercy of that service working correctly, which kinda defeats the purpose of a "unit" test. Mocking makes sense in a lot of cases.

Rendering data returned from Sqlite with React and Phonegap

I'm working on a PhoneGap app using React and SQLite locally and Parse in the cloud.
The problem I'm running into is rendering the results of the local DB query on the screen.
The root cause seems to be the fact that it is not possible to set the React component's state or locally declared variable from within the DB Success Callback.
Is there a different way to achieve this?
Here's the code, which is a part of my component's "success:" event. The problem is that sortedContacts set in the callback doesn't update the variable, and it stays with it's original value. Tried to set state - same result.
getContactsFromDB();
this.setState({contactsStatus: "",
renderedContacts: sortedContacts.sort()});
function getContactsFromDB(){
dbobj.transaction(selectRecords);
function selectRecords(tx)
{
tx.executeSql('SELECT Name FROM CONTACTS WHERE UserName = ?', [userName], successResults);
}
function successResults(tx,results){
var len = results.rows.length;
for (var i=0; i<len; i++){
sortedContacts.push(results.rows.item(i).Name);
}
}
}
I'm guessing, by the fact that you're using callbacks, that these methods are asynchronous. In that case, setState gets called before sortedContacts has been updated.
You'd need to do something like this:
var _this = this; // preserve reference to context
getContactsFromDB()
function getContactsFromDB() {
function successResults(tx,results){
_this.setState({
renderedContacts: results.rows.item.map( function(i) {
return i.Name
})
})
}
(Note: I left out some unrelated parts of your code, for brevity, and I switched the for loop to a map, because it's nicer :) )

Are stop-returning functions still stopped when inside nested functions?

In addition, the following functions which return an object with a
stop method, if called from a reactive computation, are stopped when
the computation is rerun or stopped:
Tracker.autorun (nested) Meteor.subscribe observe() and
observeChanges() on cursors
This means that the observe stops here:
Tracker.autorun ->
cursor.observe()
But what about here?
Tracker.autorun ->
Tracker.nonReactive ->
cursor.observe()
When a MiniMongo reactive handle (find(), observe(), etc.) is created in a reactive computation, MiniMongo will detect it and attach a listener to the computation's onInvalidate that will stop the handle when the computation is invalidated (or stopped).
It does not matter whether this is done directly in the autorun callback
or in a function that is called from the callback it, as long as it is done synchronously (i.e, while in the context of the same computation).
There is one notable exception: a non-reactive callback.
A Tracker.nonreactive callback is fired within a non-reactive context, so the current computation is set to null and Tracker.active is set to false. Under those conditions, MiniMongo will not attach the aforementioned listener and the change observer will not be stopped automatically.
You can stop it manually, though:
const MyCollection = new Mongo.Collection(null);
const cursor1 = MyCollection.find({foo: 'bar'});
const cursor2 = MyCollection.find({foo: 'baz'});
let observeCallback = {
added(doc) {
console.log('added', doc);
}
};
let handle = Tracker.autorun(function(c) { // c is the computation object
cursor1.observe(observeCallback); // will be automatically stopped
Tracker.nonreactive(function() {
let observer = cursor2.observe(observeCallback);
c.onStop(function() {
observer.stop(); // explicitly stops the observer
})
});
});
MyCollection.insert({foo: 'bar'});
MyCollection.insert({foo: 'baz'});
handle.stop();

Meteor: sharing variables between helpers

I am currently building a quizz app in Meteor and have three function within helpers
Template.quizPage.helpers({
//this helper finds the currentquiz and returns the currentquestion to the template
question: function(){
currentquiz = this.quiztitle;
currentQuestion = Session.get('currentQuestion') || 0;
return Quizzes.findOne({'quiztitle': currentquiz}).quizquestions[currentQuestion]
},
length: function (){
questionlength = $(Quizzes.findOne({'quiztitle': currentquiz}).quizquestions).length;
return questionlength;
},
answers: function(){
currentquiz = this.quiztitle;
currentQuestion = Session.get('currentQuestion') || 0;
return Quizzes.findOne({'quiztitle': currentquiz}).answers[1][currentQuestion]
}
});
As you can see some of this code is already duplicate (currentquiz = this.quiztitle). How can I share currentquiz between functions within a helper?
This is becoming a real problem as I need to define the variable currentquiz one time
currentQuestion = [0,0,0,0,0];
But current code resets the currentquestion any time I activate the helper in the template. I can;t define it above the function $(document.ready) wrap to set the variable or define it . This should be really easy right?
i know this is an old thread but I just came across the same problem. What you can do is define a reactive variable on the template level:
var abc;
Template.XY.rendered = function(){
abc = new ReactiveVar("abc");
},
Template.XY.helpers({
someFunction:function(){
return abc.get();
}
)}
I define a var abc; to bind the variable to the template instance (i guess this can also be done with this.abc = new ... )
To provide a variable available in the template helpers and events you just need to add it in the template instance.
In the onCreated function you can access the template instance with the keyword this.
Template.myTemplate.onCreated(function(){
this.mySharedVariable = new ReactiveVar("myValue");
});
In the helpers you can access to the templace instance with Template.instance().
Template.myTemplate.helpers({
myHelper1(){
const instance = Template.instance();
instance.mySharedVariable.get(); // Get the value "myValue"
// Do something
},
myHelper2(){
const instance = Template.instance();
instance.mySharedVariable.get(); // Get the value "myValue"
// Do something
}
});
In the events you can access to the template instance with the second parameter of the function.
Template.myTemplate.events({
'click .js-do-something' : function(event, instance){
instance.mySharedVariable.set("newValue"); // Set the value "newValue"
}
});
In the example above i'm using a ReactiveVar so if its value get changed the two helpers myHelper1 and myHelper2 will re-execute. But be free to use normal variable depending on your need.
PS: Because Session are global, you should not use Session if you only use this "shared" variable inside your template.

Meteor reactivity in functions, return session.get('variable'), runs everytime session.set('variable') is run?

Ok, let's say I have a client side function that returns a Session variable, eg:
Template.hello.random = function() {
return Session.get('variable');
};
Somewhere else let's say I have a button that does
Session.set('variable', random_number);
Everytime I hit that button, will my Template.hello.random function run? I find it hard to wrap my head around that..
All the Meteor "Magic" comes from Deps.Dependency and Deps.Computation (http://docs.meteor.com/#deps_dependency)
eg
var weather = "sunny";
var weatherDep = new Deps.Dependency;
var getWeather = function () {
weatherDep.depend()
return weather;
};
var setWeather = function (w) {
weather = w;
// (could add logic here to only call changed()
// if the new value is different from the old)
weatherDep.changed();
};
Session mirror's this pattern, but as a key/value store instead of just one value. (Actually Session is just an instance of the ReactiveDict class)
When you have a computation that calls getWeather() the .depend() call links it to the computation - and the .changed() call invalidates that computation.
eg, getting a computation via Deps.autorun()
computaton = Deps.autorun(function(){
var localWeather = getWeather();
console.log("local weather", localWeather);
});
computation.onInvalidate(function(){
console.log("a dependency of this computation changed");
});
console.log("setting weather");
setWeather("abc");
With this infrastructure - we find out that Template helpers are run in a computation - and when the dependencies of the computation are .changed(), it queues up the template to re-render.

Resources