ember-render-modifiers icon indicating copy to clipboard operation
ember-render-modifiers copied to clipboard

Timing issues without didRender

Open damatri opened this issue 4 years ago • 1 comments

In the process of rewriting components to Octane and using modifiers I ran into timing issues when didRender could no longer be used. The component in question renders text and based on if the text is scrollable or not adds an ‘expand text’ button, which when clicked shows the whole text without having to scroll.

See the following image for some explanation: truncated-toggle

  1. Short text that is not scrollable
  2. Long text that scrolls, button is shown
  3. When the long text is expanded (There is also a button to collapse the content, but forgot to add it in the image)
  4. New text is loaded, should show button

The problem occurs when you expand the text and then load in new text. Every time new text gets loaded the text gets collapsed by default, then it checks if the text is scrollable. But because it takes some time to render the collapsing it does not see the text as scrollable and no button is shown.

Component.js :

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking'

export default class TruncatedToggle extends Component {
  @tracked
  hasOverflow = false;

  @tracked
  opened = false;

  @action
  init() {
    this.opened = false;
  }

  @action
  getOverflow(element) {
    this.hasOverflow =  element.scrollHeight-1 > element.clientHeight;
  }

  @action
  buttonClicked() {
    this.opened = !this.opened;
  }
}

Previously the getOverflow code was in didRender in which the element scrollHeight and clientHeight gave correct values. Tried to set opened in the init() function to have some time between getting the overflow and collapsing the content, but that was in vain.

Component hbs:

<div class="truncated-toggle"
  {{did-update this.init @content}}
>
  <div class="truncated-toggle__content"
    {{did-insert this.getOverflow}}
    {{did-update this.getOverflow @content}}
  >
    {{{@content}}}
  </div>

  {{#if this.hasOverflow}}
    <button {{action 'buttonClicked'}} class="btn btn--default">
      {{#if this.opened}}
        Collapse text
      {{else}}
        Expand text
      {{/if}}
    </button>
  {{/if}}
</div>

I am able to make this work by adding a timeout:

  @action
  getOverflow(element) {
    setTimeout(() => {
      this.hasOverflow =  element.scrollHeight-1 > element.clientHeight;
    })
  }

But this feels a bit hacky to me. Is this the way to go, or is there a solution that I do not know about?

damatri avatar Aug 03 '21 09:08 damatri

Another example would be how to rewrite the sticky chatbox mentioned in this article: https://embermap.com/notes/63-building-a-sticky-chatbox

Which in the conclusion states:

  • The willRender and willUpdate hooks are a great place to take measurements or perform visual calculations on a component's DOM before Ember re-renders it.
  • The didRender hook is useful if you need to update a component's DOM in response to a re-render, for example after the component receives new attrs.

How will you take measurements/make calculations before you get new attributes and after new attributes have rendered?

damatri avatar Aug 10 '21 11:08 damatri