001package react4j.internal;
002
003import arez.Arez;
004import arez.Disposable;
005import arez.annotations.ArezComponent;
006import javax.annotation.Nullable;
007import zemeckis.Zemeckis;
008
009/**
010 * Utilities for interacting with the Arez scheduler.
011 */
012public final class SchedulerUtil
013{
014  /**
015   * A non-null lock that will be released in the next micro-task which will schedule any renders required.
016   */
017  @Nullable
018  private static Disposable c_schedulerLock;
019
020  private SchedulerUtil()
021  {
022  }
023
024  /**
025   * The first time an react4j view is rendered it will lock the Arez scheduler and release
026   * the lock in the micro-task immediately following the task that prompted the render. If this
027   * is not done it is possible that Arez can re-trigger a view render when the scheduler is
028   * triggered after the tracked render completes but before the render method has returned to the
029   * react runtime. This results in error message from react as a setState()/forceRender() was invoked
030   * while still within a render() method.
031   *
032   * <p>NOTE: While render methods are read-only transactions, they can un-observe components with
033   * {@link ArezComponent#disposeOnDeactivate()} set to <code>true</code> that would result in the
034   * arez component being disposed and triggering an update that would mark particular React
035   * view/Observers as STALE and trigger a re-render of that view.</p>
036   */
037  public static void pauseUntilRenderLoopComplete()
038  {
039    if ( null == c_schedulerLock )
040    {
041      c_schedulerLock = Arez.context().pauseScheduler();
042      // schedule immediately after this call stack pops.
043      Zemeckis.microTask( () -> {
044        c_schedulerLock.dispose();
045        c_schedulerLock = null;
046      } );
047    }
048  }
049}