Providing a source of reactive data - meteor

Context: My current Meteor-React project is a teaching app where a teacher can observe remotely what the learner is doing. There are many different views that the learner can use, so I need to separate the data-sharing aspect from the views themselves. The same views will be displayed on the teacher's device, with the display controlled by the student's actions.
Questions:
* Is the technique I am using sound?
* How do I prevent a component from being re-rendered when its input has not changed?
Details: I have created a bare-bones prototype (see below). This uses a Source instance (which in the app itself will be updated through a MongoDB collection) to provide reactive data for a view component. In my prototype, I simply generate random data.
I have had two surprises.
One: I discover that if I call .get() on a ReactiveVar in the source, this is enough to trigger the Tracker object to read in new values even if I return the value of a completely non-reactive variable. As can be expected, if the value of the ReactiveVar does not change, then the Tracker ignores any changes to the non-reactive variable.
Two: The value obtained by the Tracker is forwarded to the componentsprops` causing a re-render even if the value is unchanged.
Code:
import React, { Component } from 'react'
import { ReactiveVar } from 'meteor/reactive-var'
import { withTracker } from 'meteor/react-meteor-data'
/// SOURCE ——————————————————————————————————————————————————————————
class Source {
constructor() {
this.updateData = this.updateData.bind(this)
this.updateData()
}
updateData() {
const reactive = Math.floor(Math.random() * 1.25) // 4 times as many 0s as 1s
data.set(reactive)
console.log("reactive:", reactive)
this.usable = ["a", "b", "c"][Math.floor(Math.random() * 3)]
console.log("usable: ", this.usable)
setTimeout(this.updateData, 1000)
}
get() {
data.get() // We MUST get a reactive value to trigger Tracker...
return this.usable // ... but we CAN return a non-reactive value
}
}
let data = new ReactiveVar(0)
const source = new Source()
/// COMPONENT ———————————————————————————————————————————————————————
class Test extends Component{
render() {
console.log("rendered:", this.props.data)
return (
<div>
{this.props.data}
</div>
)
}
}
export default withTracker(() => {
const data = source.get()
console.log("UPDATE: ", data)
console.log("")
const props = {
data
}
return props
})(Test)
Sample Console output, with annotations:
reactive: 1
usable: b
UPDATE: b
rendered: b <<< initial value rendered
reactive: 1 <<< no change to reactive value...
usable: a <<< ...so usable value is ignored
reactive: 0 <<< reactive value changes...
usable: c <<< ... so an update is sent to the component
UPDATE: c
rendered: c <<< c rendered
reactive: 0 <<< no change to the reactive value...
usable: c
reactive: 0
usable: b
reactive: 0
usable: c
reactive: 0
usable: b
reactive: 1 <<< but when reactive value changes
usable: c <<< the usable value does not
UPDATE: c
rendered: c <<< c re-rendered, although unchanged
To recap: My plan is to increment a ReactiveVar in my Source instance each time a new datum arrives from the student. However, if the student is simply moving the cursor, then I want only the component that displays the student's cursor to re-render, and not the entire view.
I would appreciate any insights into how I can achieve this elegantly.

The behaviour you are seeing is part of the Meteor 'magic' - it sees that your reactive variable depends on a plain variable, and makes that reactive too, or more correctly it sets up a watch on it.
withTracker will often trigger multiple renders, so the best way to optimise these is use of React.memo()
I don't personally like React.memo, it feels clumsy, and makes the developer do work that feels unnecessary. There is a good article here which explains it:
https://dmitripavlutin.com/use-react-memo-wisely/
When a component is wrapped in React.memo(), React renders the
component and memoizes the result. Before the next render, if the new
props are the same, React reuses the memoized result skipping the next
rendering.

Related

Binding custom element in initial loading

In aurelia: I have a string interpulation over object property that works fine in the app.html - it shows number of accounts:
ALL ACCOUNTS (${userAccountsData.length})
In the initial loading, I see that the value changes after few milliseconds from 0 to the actual value (data is retrieving from the service), but - when trying to show aggregate data (count number of active accounts) over the same data in a template (custom element) - the data stays as 0 and not updated as the userAccountsData.length
*When refreshing again after the initial loading - the data is shown as it should be.
This is the custom element instance in the app.html:
<account-status-selection-bar accounts-data.bind="userAccountsData"></account-status-selection-bar>
And this is part of the HTML of the custom element itself:
<template>
<div ref="active"
class="selection">${accountActivationDistribution.numberOfActiveAccounts}
This is the relevant part of the custom element VM:
"use strict";
import { bindable} from 'aurelia-framework';
export class accountStatusSelectionBar {
#bindable accountsData;
constructor() {
this.accounts = [];
this.accountActivationDistribution = { numberOfActiveAccounts: 0,
numberOfInactiveAccounts : 0,
numberOfTotalAccounts : 0
}
get activeAccounts() {
var activeAccounts = this.accounts.filter(function(account) {
return account.IsApproved;
});
return activeAccounts.length;
}
attached()//bind()
{
this.accounts = this.accountsData;
this.accountActivationDistribution.numberOfActiveAccounts =
this.activeAccounts
}
In the app.js I use observerLocator - here is the code related to the working part of userAccountsData.length:
constructor() {
this.userAccountsData = [];
....
this.subscribe = this.observerLocator.getObserver(accounts, "all")
.subscribe((value) => {
if (!value)
return;
this.userAccountsData = value;
**A work around I found (although I'm not sure this is the best way) is to do the aggregation in the app.js (in the observer part) in object and bind the already aggregated object to the custom element - this is working. I'm still looking for the mentioned above solution.
It looks like the problem is you're binding userAccountsData to accountsData on your custom control; then assigning this.accounts = this.accountsData; and finally later you're reassigning userAccountsData in app.js.
Because accounts is not observing or bound to the original userAccountsData, it maintains the reference to the original array (which is set to an empty array) and never gets updated.
There is a race condition on refresh, where some cache probably means that userAccountsData gets the updated value before the binding occurs, which is why it works sometimes.
The solution is to remove some of the reassignment and just bind directly to accounts and forget the intermediate accountsData.
I created a gist here showing the different behaviour.

#ngrx/store Ignore first emitted value

store.select() emits previous store state.
Is it possible to subscribe to changes from "this point forward" without getting the previous store value?
If you are not interested in the first emitted value, you should be able to use the skip operator:
store.select(...).skip(1)...
skip operators need piping now, you can use skip like this:
store.pipe(select(...), skip(1));
In terms of the 'hacky' part, it is a standard practice in ngrx to set an initial state with properties set to null. and that value gets emitted initially. so the first value you get will be null in these cases.
Alternatively you could also consider skipwhile(https://www.learnrxjs.io/learn-rxjs/operators/filtering/skipwhile) and use it like this:
store.pipe(select(...), skipWhile(val => val === undefined));
where undefined is the initial value of the property you are interested in. Rather than setting the initial value of the property to undefined, you could use null as the initial value as well, and change the above skipwhile() accordingly.
Just sharing my thoughts (and solution) after reading #Niz's answer.
This is a perfect, practical example of how to utilize the difference between null and undefined. When you initialize your state with null, you're basically saying:
I don't care about differentiating the nullable future state from the
initial one. I don't care if the user is null because he has signed
out or because he just didn't sign in
However, in some cases this could be insufficient. Think about a case when you need an asynchronous call (implemented in effects) in order to know if you have an active user session. Based on the selection result, you should determine whether to show a login modal or redirect to a content page. With initial user state set to null, you'd pop up that modal and then immediately hide it when that asynchronous call returns a session value.
With initial state set to undefined you can make that differentiation, saying:
Initially, I know nothing about my state, then it's undefined. When I know it should be empty, then I'll set it to null.
Therefor, as a practical solution, I set everything on the app's initialState to undefined. In the example above, I need to know if the login modal should be displayed after the asynchronous call resolves. skipWhile(val => val === undefined) will do the job for sure, but repeating it over and over again feels a little tedious. Plus, it's not really descriptive to our use case. I created a rxjs-custom-operators.ts with a shortened implementation:
import { Observable } from "rxjs";
import { skipWhile } from "rxjs/operators";
export const skipInitial = () => {
return <T>(source: Observable <T>): Observable<T> => {
return source.pipe(skipWhile(value => value === undefined));
};
};
Usage:
navigateOnLoad(): void {
this.store.pipe(select(selectAuthUser), skipInitial()).subscribe((authUser: CognitoUser) => {
// Navigate to login if !authUser, else navigate to content...
});
}

Queuing asynchronous actions in reflux

When using RefluxJS stores with asynchronous actions, you can easily end up having race conditions between your actions.
Abstract description of the issue
For example, our store is in state X. An async action A is called from X, and before it finishes, another async action B is called, also from X. From here, no matter which action finishes first, it goes wrong.
B finishes first with a state Y1, A finishes last and overwrites the state Y1 with Y2.
A finishes first with a state Y2, B overwrites Y2 with Y1.
The desired behavior would be to have:
A B
X -> Y -> Z
Where B is not based on X, but on Y, and leads to a consistent Z state, instead of two actions based on the same state, leading to an inconsistent state:
A
X -> Y1 .--> Y2
\ /
'----'
B
Implemented example of the issue
I wrote a minimal working example, running with Node, of the problem I am talking about.
var Q = require('q');
var Reflux = require('reflux');
var RefluxPromise = require('reflux-promise');
Reflux.use(RefluxPromise(Q.Promise));
var AsyncActions = Reflux.createActions({
'add': { asyncResult: true }
});
var AsyncStore = Reflux.createStore({
init: function () {
// The state
this.counter = 0;
AsyncActions.add.listenAndPromise(this.onAdd, this);
},
// Increment counter after a delay
onAdd: function(n, delay) {
var that = this;
return apiAdd(this.counter, n, delay)
.then(function (newCounter) {
that.counter = newCounter;
that.trigger(that.counter);
});
}
});
// Simulate an API call, that makes the add computation. The delay
// parameter is used for testing.
// #return {Promise<Number>}
function apiAdd(counter, n, delay) {
var result = Q.defer();
setTimeout(function () {
result.resolve(counter + n);
}, delay);
return result.promise;
}
// Log the store triggers
AsyncStore.listen(console.log.bind(undefined, 'Triggered'));
// Add 3 after 1 seconds.
AsyncActions.add(3, 1000);
// Add 100 almost immediately
AsyncActions.add(100, 1);
// Console output:
// > Triggered 100
// > Triggered 3
// Desired output (queued actions):
// > Triggered 3
// > Triggered 103
With these dependencies in package.json
{
"dependencies": {
"q": "^1.3.0",
"reflux": "^0.3",
"reflux-promise": "^1"
}
}
Nature of the question
I expected RefluxJS to queue actions, but it doesn't. So I am looking for a way to order these actions correctly. But even if I managed to queue up these actions in some way (so B is issued after A) how can I be certain that, when A finishes, issuing B is still a valid action ?
Maybe I am using RefluxJS the wrong way in the first place, and this scenario does not happen in a properly structured app.
Is queuing of the asynchronous actions (assuming this is possible within a Reflux app) the solution ? Or should we work on avoiding these scenarios in the first place, somehow ?
Your example seems like more of an issue with the concept of "source of truth" than anything else. You're storing the current state of the number ONLY client side, but ONLY updating it after receiving confirmation from the server side on an operation being done to it.
Of course that'll make issues. You're mixing the actions upon the number and the storage of the number in a weird way where there's no single source of truth for what the number is at any given moment. It's in limbo between the time when the action is called finished...and that's no good.
Either store the number client side, and every time you add to it, add to that number directly and then tell the server side what the new number is... (i.e. the client side is taking responsibility as the source of truth for the number while the client side runs)
OR store the number server side, and every time you up it with an action from the client side, the server returns the new updated number. (i.e. the source of truth for the number is completely server side).
Then, even if race issues occur, you still have a source of truth for what the number is, and that source can be checked and confirmed. For example, if the server side holds the source of truth for the number then the API can also return a timestamp for the status of that value every time it returns it, and you can check it against the last value you got from the API to make sure you're ACTUALLY using the newest value.

meteor and reactJS - how to make child components reactive?

I have a child component that depends on another widget to set what data it should subscribe to. So effectively a dynamic subscription.
getMeteorData() {
var topicName = this.context.topicName;
var handle = Meteor.subscribe('RebotTopicDetail', {name: topicName});
...
If topicName changes I want to re-subscribe to new data.
What is the best way to do this?
The topicName is being set by another component, and I am using context to pass it between objects.
https://facebook.github.io/react/docs/context.html
I can see the context.topicName is changing as I am displaying it.
But it's not triggering the reactive calculation inside meteor data to rerun.
Is there a way to declare a Deps on a variable within getMeteorData?
My understanding was that was a reactive block so changes would cause the getMeteorData block to re-run.
https://react-in-meteor.readthedocs.org/en/latest/meteor-data/
I also tried passing the topicName in via props, but that also doesn't trigger a reactive update.
for others coming here, related threads
https://github.com/meteor/react-packages/issues/19
https://github.com/meteor/react-packages/issues/66
https://github.com/facebook/react/issues/2517
edit: props seems to be reactive
```
getMeteorData() {
var topicName = this.props.topicName;
var handle = Meteor.subscribe('RebotTopicDetail', {name: topicName});
/// and in the container passing in a prop
<TriggerList topicName={this.state.topicName} />
```
works. not sure if using state of the container item had something to do with it.

Adding a model to Collection in Backbone [duplicate]

I'm running into an odd issue with a Backbone.js Model where an array member is being shown as blank. It looks something like this:
var Session = Backbone.Model.extend({
defaults: {
// ...
widgets: []
},
addWidget: function (widget) {
var widgets = this.get("widgets");
widgets.push(widget);
this.trigger("change:widgets", this, widgets);
},
// ...
// I have a method on the model to grabbing a member of the array
getWidget: function (id) {
console.log(this.attributes);
console.log(this.attributes.widgets);
// ...
}
});
I then add a widget via addWidget. When trying getWidget the result I get (in Chrome) is this:
Object
widgets: Array[1]
0: child
length: 1
__proto__: Array[0]
__proto__: Object
[]
It's showing that widgets is not empty when logging this.attributes but it's shown as empty when logging this.attributes.widgets. Does anyone know what would cause this?
EDIT
I've changed the model to instantiate the widgets array in the initialization method to avoid references across multiple instances, and I started using backbone-nested with no luck.
Be careful about trusting the console, there is often asynchronous behavior that can trip you up.
You're expecting console.log(x) to behave like this:
You call console.log(x).
x is dumped to the console.
Execution continues on with the statement immediately following your console.log(x) call.
But that's not what happens, the reality is more like this:
You call console.log(x).
The browser grabs a reference to x, and queues up the "real" console.log call for later.
Various other bits of JavaScript run (or not).
Later, the console.log call from (2) gets around to dumping the current state of x into the console but this x won't necessarily match the x as it was in (2).
In your case, you're doing this:
console.log(this.attributes);
console.log(this.attributes.widgets);
So you have something like this at (2):
attributes.widgets
^ ^
| |
console.log -+ |
console.log -----------+
and then something is happening in (3) which effectively does this.attributes.widgets = [...] (i.e. changes the attributes.widget reference) and so, when (4) comes around, you have this:
attributes.widgets // the new one from (3)
^
|
console.log -+
console.log -----------> widgets // the original from (1)
This leaves you seeing two different versions of widgets: the new one which received something in (3) and the original which is empty.
When you do this:
console.log(_(this.attributes).clone());
console.log(_(this.attributes.widgets).clone());
you're grabbing copies of this.attributes and this.attributes.widgets that are attached to the console.log calls so (3) won't interfere with your references and you see sensible results in the console.
That's the answer to this:
It's showing that widgets is not empty when logging this.attributes but it's shown as empty when logging this.attributes.widgets. Does anyone know what would cause this?
As far as the underlying problem goes, you probably have a fetch call somewhere and you're not taking its asynchronous behavior into account. The solution is probably to bind to an "add" or "reset" event.
Remember that [] in JS is just an alias to new Array(), and since objects are passed by reference, every instance of your Session model will share the same array object. This leads to all kinds of problems, including arrays appearing to be empty.
To make this work the way you want, you need to initialize your widgets array in the constructor. This will create a unique widget array for each Session object, and should alleviate your problem:
var Session = Backbone.Model.extend({
defaults: {
// ...
widgets: false
},
initialize: function(){
this.set('widgets',[]);
},
addWidget: function (widget) {
var widgets = this.get("widgets");
widgets.push(widget);
this.trigger("change:widgets", this, widgets);
},
// ...
// I have a method on the model to grabbing a member of the array
getWidget: function (id) {
console.log(this.attributes);
console.log(this.attributes.widgets);
// ...
}
});
Tested in a fiddle with Chrome and Firefox: http://jsfiddle.net/imsky/XBKYZ/
var s = new Session;
s.addWidget({"name":"test"});
s.getWidget()
Console output:
Object
widgets: Array[1]
__proto__: Object
[
Object
name: "test"
__proto__: Object
]

Resources