Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blinking #25

Open
jeron-diovis opened this issue Dec 11, 2017 · 10 comments
Open

Blinking #25

jeron-diovis opened this issue Dec 11, 2017 · 10 comments

Comments

@jeron-diovis
Copy link

jeron-diovis commented Dec 11, 2017

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
Copy link
Author

sorry, misclick

@jeron-diovis jeron-diovis reopened this Dec 11, 2017
@xtuc
Copy link
Owner

xtuc commented Dec 11, 2017

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.

@jeron-diovis
Copy link
Author

jeron-diovis commented Dec 12, 2017

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.

@xtuc
Copy link
Owner

xtuc commented Dec 12, 2017

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?

@jeron-diovis
Copy link
Author

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.

@xtuc
Copy link
Owner

xtuc commented Dec 13, 2017

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?

@jeron-diovis
Copy link
Author

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.

@xtuc
Copy link
Owner

xtuc commented Dec 13, 2017

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?

@jeron-diovis
Copy link
Author

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.

@xtuc
Copy link
Owner

xtuc commented Dec 18, 2017

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants