cool
cool

Reputation: 83

React - Displaying a loading spinner for a minimum duration

I have 2 questions ...

  1. During Registration submission process, I need to display a Loading component until the response is received from the server.

The loading component is not rendering:

const Register: FC<RouteComponentProps> = () => {

  const [loading, setLoading] = useState<boolean>(false)
  const [success, setSuccess] = useState<boolean>(false)
...

const onSubmit = async(values: Values) => {

  setLoading(true)
  try {
    const response = await register({
      variables: { data: { firstName: values.first_name, lastName: values.last_name, email: values.user_email, password: values.password } }
    })
    setLoading(false)
    if (response && response.data && response.data.register) {
      setSuccess(true)
    }
  } catch (error) {

     setLoading(false)
     ...

  }
}

return (
   ...
   { loading && <Loading/> }
)

Where am I wrong, kindly let me know.

  1. Sometimes you will find that some web requests are relatively fast (< 0.5 second). In this case, the spinner will become a flicker in a web page and users don’t have enough time to understand what is happening. In order to avoid drastic web page DOM change and to reduce users’ confusion, it would be better to display the spinner for a minimum amount of time (eg, 1 second) no matter how much time it takes for loading data. How can I achieve this?

Thank you!

Upvotes: 4

Views: 4832

Answers (4)

Elijah
Elijah

Reputation: 2203

Assuming you have a way to know whether your Task result is pending, you can use the useThrottledValue hook from mantine or the useThrottle hook from use-hooks. Unlike most solutions to this problem, this solution has two crucial differences.

  1. The loading state is not always delayed, only a minimum duration will occur (i.e. if a Task takes the same time as the threshold, loadingDelayed will be true only for the ~ threshold time)
  2. The loading state is not delayed when being set to true. A comment that spoke to me when solving this problem is that it shouldn't be integrated into a library like TanStack Query since this is a UI demand, not an API/data demand. Their solution however was an oversight and didn't account that we don't want a delay when the data fetching is initiated.
const { loading: accountLoading, ... } = YOUR_OBJECT;
const accountLoadingDelayed = useThrottledValue(accountLoading, accountLoading ? 400 : 0);

Upvotes: 0

cohen
cohen

Reputation: 31

you can use with Promise.all. define your fetch and define delay with promise, and you should wait when the fetch and minimum delay will finish. in general its will be like that:

function delayPromise(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
const requestPromise= fetch(....);
const res = await Promise.all([requestPromise, delayPromise(1000)]);

and in your code I think you need change to:

const registerPromise = register({
      variables: { data: { firstName: values.first_name, lastName: values.last_name, email: values.user_email, password: values.password } }
    });
const res = await Promise.all([registerPromise, delayPromise(1000)]); // your register response found in res[0]

Upvotes: 1

darksinge
darksinge

Reputation: 1985

To show the spinner for at least 1 second or the actual request time if longer than 1 second:

const onSubmit = async(values: Values) => {
  setLoading(true)
  try {
    const now = new Date().valueOf()
    const response = await register(...)
    const waitTime = Math.max(0, -(new Date().valueOf() - now) + 1000)
    await new Promise(resolve => setTimeout(resolve, waitTime))
  } catch (error) {
     // handle error
  } finally {
     setLoading(false)
  }
}

Upvotes: 1

Sushilzzz
Sushilzzz

Reputation: 577

You dont need to show loading to min 1 second since loading icon is already shown when fetching data and in real life it will be more than 1 sec.

if u want to set min 1 sec anyways

  try {
    const response = await register({
      variables: { data: { firstName: values.first_name, lastName: values.last_name, email: values.user_email, password: values.password } }
    })
//change here
 setTimeout(() => setLoading(false), 1000);

Upvotes: 3

Related Questions