2

Let's say I have a React element <Parent> and I need to render an array of <FooBar>

My code currently looks something like

Parent = React.createClass({ getChildren() { var children = this.props.messages; // this could be 100's return children.map((child) => { return <FooBar c={child} />; }); }, render() { return ( <div> {this.getChildren()} </div> ); } }); 

This is really slow when there are 100's of children because Parent waits for all the children to render. Is there a workaround to render the children incrementally so that Parent does not have to wait for all its children to render?

1
  • 3
    I think the way to go about this is windowing, see reactjs.org/docs/… Commented Jan 12, 2018 at 14:47

1 Answer 1

1

You can take a subset of messages at a time for render, and then queue up further updates with more children via a count in state.

Using requestIdleCallback or setTimeout to queue will allow you to escape React's state batching from intercepting the current browser paint, which would be a problem if you did setState directly from componentDidUpdate

Heres something to get you going

const Parent = React.createClass({ numMessagesPerRender = 10 constructor(props) { super(props) this.state = { renderedCount: 0 } } componentWillReceiveProps(props) { // must check that if the messages change, and reset count if you do // you may also need to do a deep equality of values if you mutate the message elsewhere if (props.messages !== this.props.messages) { this.setState({renderedCount: 0}) } } getChildren() { // take only the current const children = this.props.messages.slice(0, this.state.renderedCount); return children.map(child => <FooBar c={child} />); }, render() { return ( <div> {this.getChildren()} </div> ); } renderMoreMessagesPlease() { // you MUST include an escape condition as we are calling from `componentDidXYZ` // if you dont your component will get stuck in a render loop and crash if (this.state.renderedCount < this.props.messages.length) { // queue up state change until the frame has been painted // otherwise setState can halt rendering to do batching of state changes into a single // if your browser doesnt support requestIdleCallback, setTimeout should do same trick this.idleCallbackId = requestIdleCallback(() => this.setState(prevState => ({ renderedCount: prevState.renderedCount + this.numMessagesPerRender }))) } } componentDidMount() { this.renderMoreMessagesPlease() } componentDidUpdate() { this.renderMoreMessagesPlease() } componentDidUnmount() { // clean up so cant call setState on an unmounted component if (this.idleCallbackId) { window.cancelIdleCallback(this.idleCallbackId) } } }); 
Sign up to request clarification or add additional context in comments.

2 Comments

Sorry, I forgot to mention but this is the solution that I have already implemented but it doesn't feel right and I was wondering if there was simpler solution.
The better solution would probably be more UI centric, whereby the amount of messages rendered is tied to what a user is capable of actually seeing at any one time, ie if they need to scroll to see the full list, only render items until they would become not visible, then use the scroll event to trigger this extra messages count in state. It would use a similar mechanism to this but instead use requestAnimationFrame to intercept BEFORE screen paint, measure size of the rendered messages, compare to available visible space, and queue up more to render until they become not visible

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.