async-reactor icon indicating copy to clipboard operation
async-reactor copied to clipboard

Blinking

Open jeron-diovis opened this issue 6 years ago • 10 comments

Let say I have to screens in my application. Each screen requires some own data to be loaded before it's displayed – the task async-reactor is made for. Now, I'm doing transition from Screen1 to Screen2. What happens then?

  1. Screen1 is unmounted.
  2. Reactor is rendered – it renders loader, as initial state is empty.
  3. Reactor is mounted – user sees a loader.
  4. Promise is resolved, Reactor is updated, and user sees Screen2.

Thus, user will see how first all content disappeared, then appeared after few milliseconds – even if it is very fast, it is still noticeable for an eye. It blinks, every time I'm going between screens – instead of smooth replacement of one screen with another, as React does with normal sync flow.

I wrote a helper very similar to async-reactor for our internal needs, and this blinking make me crazy. How do you handle this issue?

jeron-diovis avatar Dec 11 '17 15:12 jeron-diovis

sorry, misclick

jeron-diovis avatar Dec 11 '17 15:12 jeron-diovis

Interesting issue, thanks for your report.

How do you unmount Screen 1 and mount screen 2? It should probably be only one rendering tick.

Could you please share an example of code and your helper? I would be very happy to use your solution if that can help you.

xtuc avatar Dec 11 '17 21:12 xtuc

How do you unmount Screen 1 and mount screen 2?

Simple transition between routes, using react-router.

Here is a minimal example: https://codesandbox.io/s/717xp22391. See how background blinks and you see "LOADER" caption when opening /screen1 and /screen2, and how smoothly color is changed when opening home page. Even with 50ms delay it is essential. Even with 10ms.

and your helper?

Not sure whether it's worth to post it. For now it looks a bit complicated, but it's essence is the same – do smth async before actually rendering component. Just more bells and whistles around.

jeron-diovis avatar Dec 12 '17 08:12 jeron-diovis

Yes, I can see it blinking.

I have a similar example here. It doesn't seem to be blinking to me, could you please try?

xtuc avatar Dec 12 '17 13:12 xtuc

I didn't understand, what's the difference? It is the same example, with delay increased to 1000ms, so instead of momentary blink we can clearly see a loader while it's on screen.

jeron-diovis avatar Dec 12 '17 17:12 jeron-diovis

Sorry I may have misunderstood you then. You're saying that the loader shouldn't show up because it cause a blinking, is that correct?

xtuc avatar Dec 13 '17 07:12 xtuc

Not exactly. I'm saying that there are some cases when loader shouldn't show up and wrapped component should be rendered synchronously.

As standalone solution, everything works just as it should for now. But it becomes a problem when we have some minimal real-world requirements.

Let say we should load and render list of something which doesn't change during user session; and we want to load it lazily, only when user opens corresponding page. Looks like an ideal usecase. When user opens our page for the first time, data is loaded and it's ok that user sees loader and waits. But for the second time there is no reason to wait – it's obvious, as we have loaded everything already. If loader still will appear every time, it will look discouraging for user – "what are we waiting for?". And now, here is our problem: even if there will be no delay (request is cached, or data is put in our flux store, or whatever else like this), our component still is async, and because of this it will blink as it was shown.

Is it clear now? Logically component does everything we want (as developers). But as for UX we have a problem and dissatisfied users.

jeron-diovis avatar Dec 13 '17 12:12 jeron-diovis

Yes I understand it now, thanks.

  • The issue is that even with a delay of 0ms (or fetching from a cache) the await/async mechanism cause a bit of delay. Unfortunately I don't see what we can do in async-reactor about that, I don't have the control over the resolution delay of the Promise (the async function).

  • The solution, from my point of view the solution should be to use an "app shell" loader. Something that looks like your application but with placeholder. The blinking will still be present but probably not visible anymore. This UX sounds good but the developer will probably need to maintain to kind of loader.

What do you think of that solution?

xtuc avatar Dec 13 '17 12:12 xtuc

Something that looks like your application but with placeholder

Not sure I understand... If app layout is like <App><Header/><SomePage/><Footer/></App>, where SomePage is our async component, then loader should be like... what? Could you provide some example, please?

I don't see what we can do in async-reactor about that

Yep, in usecase shown in your docs there is nothing to do indeed. Because async function is, well, always async =) Actually, I never used approach like this, in my case component itself and async operation it requires are always separated. So as "desperate" solution for my own "reactor" I thought to add one more option – a function, which is required to be sync and which will check whether component has enough data to render. Like this:

options => Component => {
  class Reactor extends React.Component {
    constructor(props) {
      super(props)
      this.state = { ready: options.ready(this.props) }
    }

    render() {
      const { Loader } = options
      return this.state.ready ? <Component {...this.props} /> : <Loader/>
    }

    async componentWillMount() {
      if (!this.state.ready) {
        await options.resolve(this.props)
        // of course, should also check whether we're mounted now
        this.setState({ ready: true }) 
     }
    }
  }
}

Not perfect, requires every time to remember about two places when logic changes, and as for ajax requests requires to explicitly store somewhere metadata about them (whether this resource was loaded). It's far from ideal component which does all the things itself, but it's the only working idea I have.

jeron-diovis avatar Dec 14 '17 08:12 jeron-diovis

Something that looks like your application but with placeholder

A loader that doesn't make your app blink, like the following example:

  • screenshot from 2017-12-18 08-21-19
  • screenshot from 2017-12-18 08-21-30

async-reactor was made for async components, that why I didn't mentioned that we could just add a synchronous way to render your components. That would allow you to keep with the same API. Would that solve your problem?

xtuc avatar Dec 18 '17 07:12 xtuc