Skip to content
This repository has been archived by the owner on Oct 12, 2020. It is now read-only.

check interaction with render modifiers #203

Open
lifeart opened this issue Feb 1, 2020 · 9 comments
Open

check interaction with render modifiers #203

lifeart opened this issue Feb 1, 2020 · 9 comments
Labels
help wanted Extra attention is needed question Further information is requested

Comments

@lifeart
Copy link
Owner

lifeart commented Feb 1, 2020

// background - emberjs/ember.js#18698 (comment)
https://github.com/lifeart/ember-ref-modifier/blob/master/tests/dummy/app/components/glimmer-ref.js

since we setting property in next runloop, such code may fail

 <div {{ref this "menuElement"}} {{did-insert this.focusOnFirstItem}} >

in other side, callback may solve issue

<div {{ref this.setMenuElement}} >
<div {{did-insert this.setMenuElement}} >
@action
setMenuElement(node) {
  this.menuElement  = node;
  this.focusOnFirstItem(node);
}

in other way, we can make next runloop sheduling optional, or remove it at all from modifier itself and give control to developers.

{{ref this "prop"}} - for classic usage
{{ref this "prop" run="next"}}  - for runloop control
{{ref this.setElement run="next"}} - runloop controlled callback
{{ref (next-runloop this.setElement)}} - manual callback control

other way - allow tracked chain computations rerun in same transaction if some of tracked properties is DOM node, but it seems low-level change.

@lifeart lifeart added help wanted Extra attention is needed question Further information is requested labels Feb 1, 2020
@lifeart
Copy link
Owner Author

lifeart commented Feb 1, 2020

@jrjohnson
Copy link
Collaborator

Unfortunately because of the upstream assertion this is going to be difficult to use either way because {{ref may only stop working when some set of re-rendering conditions is met which may not exist when the modifier is first added to the page, but only later when it is used in a render calculation somewhere.

My personal opinion is that this modifier should not invoke run.next() by default, but should provide an option to use it when setting the value would modify the @tracked chain. I think the current behavior is too surprising and having this hidden asynchronous set makes for difficult to track down timing bugs. (I've just spent a few hours tracking one down in ember-popper),

If {{ref (next-runloop this.setElement)}} is possible, it's a nice syntax. I think because of the callback set being available already that {{ref this.setElement}} with a setter like

@action
setElement(node){
  //set 'prop' in the next runloop since it is needed in this.calculateSize and shouldn't cause a re-rendering calculation
  run.next(()=> {
    this.prop = node
  });
}

Is obviously more verbose, but fairly clear and readable and doable today with no changed.

@lifeart
Copy link
Owner Author

lifeart commented Mar 11, 2020

I agree with all conserns, but for ember newcomers tracking-down errors like
image it pretty hard case. And write run.next in each usage is pretty uncomfortable case. Personally, I would prefer run modifiers in another transaction at all, instead of at the end of rendering transaction cycle. Because there is no official way to interop with DOM nodes/system apis and tracked stack at all.

@jrjohnson
Copy link
Collaborator

I agree, that message is very difficult to debug. On the other hand running all modifier in a new transaction (or with run.next() causes timing issues that get no visible warning, they just fail sometimes. Given these two outcomes I think the default should be to run synchronously with clear documentation for dealing with this message when it comes up.

@pzuraq
Copy link

pzuraq commented Mar 11, 2020

FWIW, using the runloop like this is a major codesmell and I highly recommend avoiding it unless absolutely necessary. This may point toward needing to write more modifiers directly, and the ref pattern may just not be an ideal way to write Ember apps.

Basically, every time you do run.next, you are opting-in to a second render pass. That is cheap enough thanks to autotracking, but its still far more expensive than necessary. There are usually ways to accomplish what you want to without multiple passes, and the recommendation would be to organize code like this.

Custom modifiers are currently painful to write, since they are all global. I think template imports will help a lot with this.

@lifeart
Copy link
Owner Author

lifeart commented Mar 11, 2020

@pzuraq is it make sence to complete autotracking frame before modifiers run?

@lifeart
Copy link
Owner Author

lifeart commented Mar 11, 2020

or run next without triggering rerender (it can be triggered if property marked as tracked)

@lifeart
Copy link
Owner Author

lifeart commented Mar 11, 2020

oor, way to check is property tracked or not (@pzuraq) if tracked - run in next runloop, if not - just assign
(have no idea how check property tracking status)

function ref(node, [  context, key ]) {
   if (isTracked(context, key)) {
     run.next(()=>context[key] = node);
   } else {
     context[key] = node;
  }
}

@pzuraq
Copy link

pzuraq commented Mar 11, 2020

I don't think we will be exposing that functionality. Unidirectional dataflow is a good thing, not a bug to work around.

Edit: Moreover, it doesn't scale to other forms of root state. What if tracking is happening through a TrackedMap or TrackedArray?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants