Incidentally - I just saw your Babel transpiler React one - very cool!
What a coincidence, I work in the Mortgage industry =)
The key point is that you have circular references and thus āsomethingās got to giveā. We deal with situations like this all the time. Hereās a not so made up example:
You have 3 interdependent fields: Loan Amount
, Total Years
, and Monthly Payment
(letās forget interest for simplicityās sake). And so the formula is:
Monthly Payment = Loan Amount / (TotalYears * 12)
Fair enough, the problem now is that any value affects the other so, again, āsomethingās got to giveā. So we (a business analyst actually) are going to decree the following 2 things:
- The value entered by the user is always right and thus canāt change in our computations.
- Loan Amount is more important than Total Years, and Total Years is more important than Monthly Payment.
With that we can construct our component:
App({
loanAmount: 200000,
loanAmountFocus: false,
totalYears: 30,
totalYearsFocus: false,
monthlyPayment: 555.56,
monthlyPaymentFocus: false,
autorun: [
function() {
// Watch for changes in Loan Amount
const loanAmount = this.loanAmount();
// Only do this if the user is changing Loan Amount
if (!this.loanAmountFocus()) return;
const totalYears = this.totalYears.value;
// We "decree" that Total Years is more important than Monthly Payment
// And thus we only update Monthly Payment based on Total Years
this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
},
function() {
// Watch for changes in Total Years
const totalYears = this.totalYears();
// Only do this if the user is changing Total Years
if (!this.totalYearsFocus()) return;
const loanAmount = this.loanAmount.value;
// We "decree" that Loan Amount is more important than Monthly Payment
// And thus we only update Monthly Payment based on Loan Amount
this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
},
function() {
// Watch for changes in Monthly Payment
const monthlyPayment = this.monthlyPayment();
// Only do this if the user is changing Monthly Payment
if (!this.monthlyPaymentFocus()) return;
const loanAmount = this.loanAmount.value;
// We "decree" that Loan Amount is more important than Total Years
// And thus we only update Total Years based on Loan Amount
this.totalYears( (loanAmount / ( monthlyPayment * 12 )).toFixed(2) );
}
],
render(){
<div>
Loan Amount: <input b="value: loanAmount, focus: loanAmountFocus"/><br />
Total Years: <input b="value: totalYears, focus: totalYearsFocus"/><br />
Monthly Payment: <input b="value: monthlyPayment, focus: monthlyPaymentFocus"/><br />
<button b="click: reset">Reset Values</button>
</div>
}
});
Hereās the repro: https://github.com/ManuelDeLeon/mortgage-calculator
In our case we offload such computations to a calc engine (a service/mixin). We donāt do it in the component itself but the logic is the same. We just pass the calc engine the value the user is working on and āfreezeā it.
Thanks! I absolutely love working with viewmodel-react
. Even after months of working with it, I honestly smile when I see my code. Thereās something right about seeing ājust my codeā. The main page of viewmodel.org has the React documentation.
In your case I would not bind input fields to state directly and eliminate a notion of derived
property.
In your case properties should not āobserveā anything and simply hold the state.
I would submit input events to controller or dispatcher that would detect which input was modified and what consequences must follow.
Based on those colculations it should submit new values to state properties. Via āactionsā or whatever.
Whatās good with this approach is that you can actually determine what was the cause of state change via logging dispatcher actions.
Could you expand on that
and that
Our system has a loan object which is basically a bag of fields related to a mortgage. It has information about borrowers, properties, prices, interest rates, etc. As you mentioned, we canāt just use computed fields for everything because a lot of the fields are interdependent.
The calc engine is basically a set of functions that contain all the business logic regarding how fields are related to one another. At risk of oversimplification, you give it a loan object plus the new field that needs to change, and it will give you a new loan object based on the existing rules.
By freeze I mean make sure the calc engine doesnāt change the value the user just entered. We use the term ālock a valueā too. We also have the ability to freeze/lock multiple values at the same time (while the calc engine does its thing). Again itās all based on business rules.
Still on the Blaze ViewModel here for a while! Thanks for your hard work.
Incidentally, I use just the same approach.
I store all the business logic that is used by several components in the global Logic
object which is itself a set of pure functions.
If business logic is unique to the component though - I do keep it inside the component.
Whatās the best way to use a Subscription Cache/Manager with ViewModel, especially in a Meteor 1.3+ project? At this point, weāre only looking to implement a global cache. Any pointers to how to go about this would be really helpful. Thanks.
Please clarify what you mean with āSubscription Cache/Managerā and āglobal cacheā. The first thing that comes to mind is redux.
Iām still using Blaze atm, so Iām trying to understand how to use ViewModel with something like Subs-Manager(https://github.com/kadirahq/subs-manager) or Subs-Cache (https://github.com/ccorcos/meteor-subs-cache) to cache the results of subscriptions to Meteor publications.
By global cache, Iām specifically referring to the note mentioned in the Subs-Manager documentation (https://github.com/kadirahq/subs-manager#patterns-for-using-subsmanager) about how it can be used. In this case, Iām just looking to instantiate a single Single subscription manager for all the subscriptions Iām planning to cache, rather than multiple Subscription managers.
Iām trying to improve the responsiveness of the app when moving between a list view and a detail view and back, and waiting for the list view to re-query and then render the list, which is a noticeable delay. For some reason, my brainās freezing up in terms of the scoping and figuring out how exactly to instantiate and use this within ViewModel bindings.
Hope this clarifies what Iām asking.
The process is pretty much the same as without VM. I tend to be more hands on when it comes to subscriptions. If I know a child component is likely to be used then I load the data in the parent. In your case you can just create a single subscription manager in a file (exporting it), and then import it wherever you need to use it. Maybe Iām missing something.
Got it. Thanks. For some reason, I overlooked this.
Sorry for the noob question. Still getting to grips with Meteor 1.3+ patterns.
I agree.
This is an amazing piece of work, and definitely the most elegant viewmodel layer iāve ever seen and ive been developing js for 7+ years and php for 3-4 before thatā¦
Manuel is a boss.