[javascript] Delayed rendering of React components

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?

This question is related to javascript reactjs higher-order-components

The answer is


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

_x000D_
_x000D_
import { useEffect, useRef, useCallback } from 'react';_x000D_
_x000D_
const useTimeout = (callback, delay) => {_x000D_
  // save id in a ref_x000D_
  const timeoutId = useRef('');_x000D_
_x000D_
  // save callback as a ref so we can update the timeout callback without resetting the clock_x000D_
  const savedCallback = useRef();_x000D_
  useEffect(_x000D_
    () => {_x000D_
      savedCallback.current = callback;_x000D_
    },_x000D_
    [callback],_x000D_
  );_x000D_
_x000D_
  // clear the timeout and start a new one, updating the timeoutId ref_x000D_
  const reset = useCallback(_x000D_
    () => {_x000D_
      clearTimeout(timeoutId.current);_x000D_
_x000D_
      const id = setTimeout(savedCallback.current, delay);_x000D_
      timeoutId.current = id;_x000D_
    },_x000D_
    [delay],_x000D_
  );_x000D_
_x000D_
  useEffect(_x000D_
    () => {_x000D_
      if (delay !== null) {_x000D_
        reset();_x000D_
_x000D_
        return () => clearTimeout(timeoutId.current);_x000D_
      }_x000D_
    },_x000D_
    [delay, reset],_x000D_
  );_x000D_
_x000D_
  return { reset };_x000D_
};
_x000D_
_x000D_
_x000D_

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

_x000D_
_x000D_
import { useState, useEffect } from 'react';_x000D_
_x000D_
const useDelayNextChildren = (children, delay) => {_x000D_
  const [finalChildren, setFinalChildren] = useState(children);_x000D_
_x000D_
  const { reset } = useTimeout(() => {_x000D_
    setFinalChildren(children);_x000D_
  }, delay);_x000D_
_x000D_
  useEffect(_x000D_
    () => {_x000D_
      reset();_x000D_
    },_x000D_
    [reset, children],_x000D_
  );_x000D_
_x000D_
  return finalChildren || children || null;_x000D_
};
_x000D_
_x000D_
_x000D_

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:

_x000D_
_x000D_
const useDelayNextChildren = (children, delay) => {_x000D_
  const [finalChildren, setFinalChildren] = useState(null); // initial state set to null_x000D_
_x000D_
  // ... stays the same_x000D_
_x000D_
  return finalChildren || null;  // remove children from return_x000D_
};
_x000D_
_x000D_
_x000D_

and using the above hook, your entire child component becomes

_x000D_
_x000D_
import React, { memo } from 'react';_x000D_
import { useDelayNextChildren } from 'hooks';_x000D_
_x000D_
const Child = ({ delay }) => useDelayNextChildren(_x000D_
  <div>_x000D_
    ... Child JSX goes here_x000D_
    ... etc_x000D_
  </div>_x000D_
  , delay_x000D_
);_x000D_
_x000D_
export default memo(Child);
_x000D_
_x000D_
_x000D_

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

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

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


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>
 )

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

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.


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.


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.


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.


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(() => {
    setTimeout(() => {
      setIsShown(true);
    }, waitBeforeShow);
  }, [waitBeforeShow]);

  return isShown ? children : null;
};

export default Delayed;

Just wrap another component into Delayed

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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to higher-order-components

Functions are not valid as a React child. This may happen if you return a Component instead of from render How do I conditionally add attributes to React components? Delayed rendering of React components Can you force a React component to rerender without calling setState? Hide/Show components in react native How to print React component on click of a button? React / JSX Dynamic Component Name Update style of a component onScroll in React.js react-router - pass props to handler component React component not re-rendering on state change