React - Throttle/debounce spinner (loading message) - not show if request is faster than X milliseconds
Asked Answered
M

3

9

I'm preparing spinners in my react app.

It works great. However, some UX tips say, that spinner/loader/etc should be displayed after some waiting time. For this example, let's say it should be 750ms.

How can I throttle/debounce (I'm still not sure what is the difference) re-render component?

Edit xlxk611rzw

In above example, loading state should not be appear anytime.

Margarite answered 30/7, 2018 at 21:18 Comment(1)
hey, here is a simple demo to clarify this topic once and for all demo.nimius.net/debounce_throttleMargeret
C
7

You could create a DelayedSpinner component that starts a timer in componentDidMount and after it elapsed renders the spinner:

class DelayedSpinner extends Component {
    state = {
        showSpinner: false,
    };

    componentDidMount() {
        this.timer = setTimeout(
            () => this.setState({showSpinner: true}), 
            this.props.delay
        );
    }

    componentWillUnmount() {
        clearTimeout(this.timer);
    }

    render() {
        return this.state.showSpinner && <Spinner />;
    }
}

Usage:

render() {
    if (loading) return <DelayedSpinner delay={750} />

    return(
        {/* render loaded data */}
    );
}

Then you can render that spinner after you kicked off the request and it will only show after a certain delay.

Ceciliacecilio answered 30/7, 2018 at 21:32 Comment(6)
You're proposition of solving the problem is simple as f... I love it!Recoverable
@MateuszJagiełło Thanks. I forgot to include invalidation of the timer in case the Spinner gets unmounted before the delay elapsed. Make sure to include that or you will receive an ugly warning notifying you that it tried to call setState on an unmounted component. I updated my answer accordingly.Ceciliacecilio
why do you wrap render returned value in {} brackets?Margeret
@Margeret It is a computed value by evaluating the condition inside the {} brackets. It wouldn't be valid jsx if you don't wrap it into brackets. It's the same reason you would wrap a variable like {this.props.something}.Ceciliacecilio
@Ceciliacecilio but there is no any JSX tag yet, it can be simplified to return this.state.showSpinner && <Spinner />Margeret
@Margeret You are correct. Updated my answer with the simplified expression.Ceciliacecilio
M
15

With Hooks and effects:

import React, { useEffect, useState } from 'react';

const DelayedSpinner = ({ size }) => {
  const [showSpinner, setShowSpinner] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => setShowSpinner(true), 750);

    return () => clearTimeout(timer);
  });

  return showSpinner && <Spinner size={size} color="primary" />;
};

export default DelayedSpinner;
Marylouisemaryly answered 17/9, 2019 at 7:38 Comment(0)
C
7

You could create a DelayedSpinner component that starts a timer in componentDidMount and after it elapsed renders the spinner:

class DelayedSpinner extends Component {
    state = {
        showSpinner: false,
    };

    componentDidMount() {
        this.timer = setTimeout(
            () => this.setState({showSpinner: true}), 
            this.props.delay
        );
    }

    componentWillUnmount() {
        clearTimeout(this.timer);
    }

    render() {
        return this.state.showSpinner && <Spinner />;
    }
}

Usage:

render() {
    if (loading) return <DelayedSpinner delay={750} />

    return(
        {/* render loaded data */}
    );
}

Then you can render that spinner after you kicked off the request and it will only show after a certain delay.

Ceciliacecilio answered 30/7, 2018 at 21:32 Comment(6)
You're proposition of solving the problem is simple as f... I love it!Recoverable
@MateuszJagiełło Thanks. I forgot to include invalidation of the timer in case the Spinner gets unmounted before the delay elapsed. Make sure to include that or you will receive an ugly warning notifying you that it tried to call setState on an unmounted component. I updated my answer accordingly.Ceciliacecilio
why do you wrap render returned value in {} brackets?Margeret
@Margeret It is a computed value by evaluating the condition inside the {} brackets. It wouldn't be valid jsx if you don't wrap it into brackets. It's the same reason you would wrap a variable like {this.props.something}.Ceciliacecilio
@Ceciliacecilio but there is no any JSX tag yet, it can be simplified to return this.state.showSpinner && <Spinner />Margeret
@Margeret You are correct. Updated my answer with the simplified expression.Ceciliacecilio
D
2

you can use setTimeout and clearTimeout to debounce

componentDidMount() {
    let debounceTime = 100;
    let timeoutId = setTimeout(() => this.setState({ loading: true }), debounceTime);
    fakeApiCall().then(() => {
      clearTimeout(timeoutId);
      this.setState({
        loading: false
      })
    });
  }

it will set loading to true once the debounce time has gone. If the request takes less time, it will be cleared in the promise.

You might want to add a new state as of "initializing" or something of the sort; otherwise if you start your application in just loading: false (while waiting for the debouncer) you will initially see the "Ok, got data" message

moreover, you can make the component configurable and take debounce time from props

Demp answered 30/7, 2018 at 21:30 Comment(1)
I've already got initializing state. Example is just example. :) Thanks.Recoverable

© 2022 - 2024 — McMap. All rights reserved.