Delayed rendering of React components

JavascriptReactjs

Javascript Problem Overview


I have a React component with a number of child components in it. I want to render the child components not at once but after some delay (uniform or different for each of the children).

I was wondering - is there a way how to do this?

Javascript Solutions


Solution 1 - Javascript

I think the most intuitive way to do this is by giving the children a "wait" prop, which hides the component for the duration that was passed down from the parent. By setting the default state to hidden, React will still render the component immediately, but it won't be visible until the state has changed. Then, you can set up componentWillMount to call a function to show it after the duration that was passed via props.

var Child = React.createClass({
	getInitialState : function () {
		return({hidden : "hidden"});
	},
	componentWillMount : function () {
        var that = this;
		setTimeout(function() {
			that.show();
		}, that.props.wait);
	},
	show : function () {
		this.setState({hidden : ""});
	},
    render : function () {
        return (
            <div className={this.state.hidden}>
                <p>Child</p>
            </div>
        )
    }
});

Then, in the Parent component, all you would need to do is pass the duration you want a Child to wait before displaying it.

var Parent = React.createClass({
    render : function () {
        return (
            <div className="parent">
                <p>Parent</p>
                <div className="child-list">
					<Child wait={1000} />
					<Child wait={3000} />
					<Child wait={5000} />
                </div>
            </div>
        )
    }
});

Here's a demo

Solution 2 - Javascript

Another approach for a delayed component:

Delayed.jsx:

import React from 'react';
import PropTypes from 'prop-types';

class Delayed extends React.Component {

    constructor(props) {
        super(props);
        this.state = {hidden : true};
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({hidden: false});
        }, this.props.waitBeforeShow);
    }

    render() {
        return this.state.hidden ? '' : this.props.children;
    }
}

Delayed.propTypes = {
  waitBeforeShow: PropTypes.number.isRequired
};

export default Delayed;

Usage:

 import Delayed from '../Time/Delayed';
 import React from 'react';

 const myComp = props => (
     <Delayed waitBeforeShow={500}>
         <div>Some child</div>
     </Delayed>
 )

Solution 3 - Javascript

I have created Delayed component using Hooks and TypeScript

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

type Props = {
  children: React.ReactNode;
  waitBeforeShow?: number;
};

const Delayed = ({ children, waitBeforeShow = 500 }: Props) => {
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => {
      setIsShown(true);
    }, waitBeforeShow);
    return () => clearTimeout(timer);
  }, [waitBeforeShow]);

  return isShown ? children : null;
};

export default Delayed;

Just wrap another component into Delayed

export const LoadingScreen = () => {
  return (
    <Delayed>
      <div />
    </Delayed>
  );
};

Solution 4 - Javascript

In your father component <Father />, you could create an initial state where you track each child (using and id for instance), assigning a boolean value, which means render or not:

getInitialState() {
    let state = {};
    React.Children.forEach(this.props.children, (child, index) => {
        state[index] = false;
    });
    return state;
}

Then, when the component is mounted, you start your timers to change the state:

componentDidMount() {
    this.timeouts = React.Children.forEach(this.props.children, (child, index) => {
         return setTimeout(() => {
              this.setState({ index: true; }); 
         }, child.props.delay);
    });
}

When you render your children, you do it by recreating them, assigning as a prop the state for the matching child that says if the component must be rendered or not.

let children = React.Children.map(this.props.children, (child, index) => {
    return React.cloneElement(child, {doRender: this.state[index]});
});

So in your <Child /> component

render() {
    if (!this.props.render) return null;
    // Render method here
}

When the timeout is fired, the state is changed and the father component is rerendered. The children props are updated, and if doRender is true, they will render themselves.

Solution 5 - Javascript

Using the useEffect hook, we can easily implement delay feature while typing in input field:

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

function Search() {
  const [searchTerm, setSearchTerm] = useState('')

  // Without delay
  // useEffect(() => {
  //   console.log(searchTerm)
  // }, [searchTerm])

  // With delay
  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      console.log(searchTerm)
      // Send Axios request here
    }, 3000)
    
    // Cleanup fn
    return () => clearTimeout(delayDebounceFn)
  }, [searchTerm])

  return (
    <input
      autoFocus
      type='text'
      autoComplete='off'
      className='live-search-field'
      placeholder='Search here...'
      onChange={(e) => setSearchTerm(e.target.value)}
    />
  )
}

export default Search

Solution 6 - Javascript

Depends on your use case.

If you want to do some animation of children blending in, use the react animation add-on: https://facebook.github.io/react/docs/animation.html Otherwise, make the rendering of the children dependent on props and add the props after some delay.

I wouldn't delay in the component, because it will probably haunt you during testing. And ideally, components should be pure.

Solution 7 - Javascript

We can solve this using Hooks:

First we'll need a timeout hook for the delay.

This one is inspired by Dan Abramov's useInterval hook (see Dan's blog post for an in depth explanation), the differences being:

  1. we use we setTimeout not setInterval
  2. we return a reset function allowing us to restart the timer at any time

import { useEffect, useRef, useCallback } from 'react';

const useTimeout = (callback, delay) => {
  // save id in a ref
  const timeoutId = useRef('');

  // save callback as a ref so we can update the timeout callback without resetting the clock
  const savedCallback = useRef();
  useEffect(
    () => {
      savedCallback.current = callback;
    },
    [callback],
  );

  // clear the timeout and start a new one, updating the timeoutId ref
  const reset = useCallback(
    () => {
      clearTimeout(timeoutId.current);

      const id = setTimeout(savedCallback.current, delay);
      timeoutId.current = id;
    },
    [delay],
  );

  useEffect(
    () => {
      if (delay !== null) {
        reset();

        return () => clearTimeout(timeoutId.current);
      }
    },
    [delay, reset],
  );

  return { reset };
};

and now we need a hook which will capture previous children and use our useTimeout hook to swap in the new children after a delay

import { useState, useEffect } from 'react';

const useDelayNextChildren = (children, delay) => {
  const [finalChildren, setFinalChildren] = useState(children);

  const { reset } = useTimeout(() => {
    setFinalChildren(children);
  }, delay);

  useEffect(
    () => {
      reset();
    },
    [reset, children],
  );

  return finalChildren || children || null;
};

Note that the useTimeout callback will always have the latest children, so even if we attempt to render multiple different new children within the delay time, we'll always get the latest children once the timeout finally completes.

Now in your case, we want to also delay the initial render, so we make this change:

const useDelayNextChildren = (children, delay) => {
  const [finalChildren, setFinalChildren] = useState(null); // initial state set to null

  // ... stays the same

  return finalChildren || null;  // remove children from return
};

and using the above hook, your entire child component becomes

import React, { memo } from 'react';
import { useDelayNextChildren } from 'hooks';

const Child = ({ delay }) => useDelayNextChildren(
  <div>
    ... Child JSX goes here
    ... etc
  </div>
  , delay
);

export default memo(Child);

or if you prefer: ( dont say i havent given you enough code ;) )

const Child = ({ delay }) => {
  const render = <div>... Child JSX goes here ... etc</div>;

  return useDelayNextChildren(render, delay);
};

which will work exactly the same in the Parent render function as in the accepted answer

...

except the delay will be the same on every subsequent render too,

AND we used hooks, so that stateful logic is reusable across any component

...

...

use hooks. :D

Solution 8 - Javascript

My use case might be a bit different but thought it might be useful to post a solution I came up with as it takes a different approach.

Essentially I have a third party Popover component that takes an anchor DOM element as a prop. The problem is that I cannot guarantee that the anchor element will be there immediately because the anchor element becomes visible at the same time as the Popover I want to anchor to it (during the same redux dispatch).

One possible fix was to place the Popover element deeper into the component tree than the element it was to be anchored too. However, that didn't fit nicely with the logical structure of my components.

Ultimately I decided to delay the (re)render of the Popover component a little bit to ensure that the anchor DOM element can be found. It uses the function as a child pattern to only render the children after a fixed delay:

import { Component } from 'react'
import PropTypes from 'prop-types'

export default class DelayedRender extends Component {
    componentDidMount() {
        this.t1 = setTimeout(() => this.forceUpdate(), 1500)
    }

    componentWillReceiveProps() {
        this.t2 = setTimeout(() => this.forceUpdate(), 1500)
    }

    shouldComponentUpdate() {
        return false
    }

    componentWillUnmount() {
        clearTimeout(this.t1)
        clearTimeout(this.t2)
    }

    render() {
        return this.props.children()
    }
}

DelayedRender.propTypes = {
    children: PropTypes.func.isRequired
}

It can be used like this:

<DelayedRender>
    {() =>
        <Popover anchorEl={getAnchorElement()}>
            <div>Hello!</div>
        </Popover>
    )}}
</DelayedRender>

Feels pretty hacky to me but works for my use case nevertheless.

Solution 9 - Javascript

render the child components not at once but after some delay .

The question says delay render but if it is ok to render but hide...

You can render the components from a map straight away but use css animation to delay them being shown.

@keyframes Jumpin {
 0% { opacity: 0; }
 50% { opacity: 0; }
 100% { opacity: 1; }
}
// Sass loop code
@for $i from 2 through 10 {
 .div .div:nth-child(#{$i}) {
  animation: Jumpin #{$i * 0.35}s cubic-bezier(.9,.03,.69,.22);
 }
}

The child divs now follow each other with a slight delay.

Solution 10 - Javascript

I have another here with a fallback option like Suspense

import { useState, useEffect } from "react";

export default function FakeSuspense(props) {
  const { children, delay, fallback } = props;
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setIsShown(true);
    }, delay);
  }, [delay]);

  return isShown ? children : fallback;
}

then use it

<FakeSuspense delay={1700} fallback={<Spinner />}>
  <Component />
</FakeSuspense>

Solution 11 - Javascript

Very nice loop I am using. One big plus with this function is that it does not repeat itself when it's not needed anymore.

function geniusLoop()
{
    console.log("TRYING TO LOOP")
    if (iFinishedMyTask == false)
    {
        setTimeout(function () { geniusLoop(); }, 1000);
        console.log("I LOOPED")
        DoSomething();
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmichalvalasekView Question on Stackoverflow
Solution 1 - JavascriptMichael ParkerView Answer on Stackoverflow
Solution 2 - JavascriptgoulashsoupView Answer on Stackoverflow
Solution 3 - JavascriptBlackView Answer on Stackoverflow
Solution 4 - JavascriptgcedoView Answer on Stackoverflow
Solution 5 - JavascriptHarshalView Answer on Stackoverflow
Solution 6 - JavascriptMarkusView Answer on Stackoverflow
Solution 7 - JavascriptMatt WillsView Answer on Stackoverflow
Solution 8 - JavascriptdjskinnerView Answer on Stackoverflow
Solution 9 - JavascriptR.MeredithView Answer on Stackoverflow
Solution 10 - JavascriptinuxView Answer on Stackoverflow
Solution 11 - JavascriptVolfraView Answer on Stackoverflow