How do I avoid Stack Overflow with circular references in Backbone.js?
-
27-05-2021 - |
Question
Two attributes in my model are supposed to update each other when changed, once. The startDate
and endDate
. Their values are Moment objects, from the moment.js library.
Two Moment objects of identical dates are not equivalent, and so this causes an loop that exceeds the maximum stack, because they appear to Backbone to have always changed.
new moment('01/01/12') == new moment('01/01/12') // => false
The { silent: true }
option seems not to help, I think because it only defers the change event rather than suppressing it alltogether, though I'm not sure.
Here's the code that overflows:
class Paydirt.Models.BlockBrowser extends Backbone.Model
initialize: =>
@on('change:startDate', @updateEndDate)
@on('change:endDate', @updateStartDate)
updateStartDate: =>
@set({ startDate: @cloneEndDate().subtract('days', @get('interval')) }, { silent: true }
updateEndDate: =>
@set({ endDate: @cloneStartDate().add('days', @get('interval')) }, { silent: true } )
cloneStartDate: => new moment(@get('startDate'))
cloneEndDate: => new moment(@get('endDate'))
I can set a global flag to prevent the callback loop as in this variation:
updateStartDate: =>
if !@changing
@changing = true
@set({ startDate: @cloneEndDate().subtract('days', @get('interval')) }, { silent: true } )
@changing = false
updateEndDate: =>
if !@changing
@changing = true
@set({ endDate: @cloneStartDate().add('days', @get('interval')) }, { silent: true } )
@changing = false
... but this is obviously a hackety solution. Is there a better pattern for me to follow in this use case?
Thank you.
Solution
Another idea:
Are you using Backbone v0.9.2? it looks like it is doing a more intensive use of the options.silent
. Look here.
The options.silent
behavior you're describing looks more like v0.9.1.
OTHER TIPS
Two ideas:
Overwrite the Underscore method
_.isEqual
to manage properly yourMoment
objects. You can use the Proxy pattern.Use custom events to have more control that when they are triggered.
I'm not sure how to do this in backbone, but you can compare the integer value of the moments
(moment().valueOf() === moment().valueOf()) // true
or
(+moment() === +moment) // true