How to scroll to bottom in react?

Reactjs

Reactjs Problem Overview


I want to build a chat system and automatically scroll to the bottom when entering the window and when new messages come in. How do you automatically scroll to the bottom of a container in React?

Reactjs Solutions


Solution 1 - Reactjs

As Tushar mentioned, you can keep a dummy div at the bottom of your chat:

render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}

and then scroll to it whenever your component is updated (i.e. state updated as new messages are added):

scrollToBottom = () => {
  this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

I'm using the standard Element.scrollIntoView method here.

Solution 2 - Reactjs

I just want to update the answer to match the new React.createRef() method, but it's basically the same, just have in mind the current property in the created ref:

class Messages extends React.Component {

  const messagesEndRef = React.createRef()

  componentDidMount () {
    this.scrollToBottom()
  }
  componentDidUpdate () {
    this.scrollToBottom()
  }
  scrollToBottom = () => {
    this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
  }
  render () {
    const { messages } = this.props
    return (
      <div>
        {messages.map(message => <Message key={message.id} {...message} />)}
        <div ref={this.messagesEndRef} />
      </div>
    )
  }
}

UPDATE:

Now that hooks are available, I'm updating the answer to add the use of the useRef and useEffect hooks, the real thing doing the magic (React refs and scrollIntoView DOM method) remains the same:

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

const Messages = ({ messages }) => {

  const messagesEndRef = useRef(null)

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
  }

  useEffect(() => {
    scrollToBottom()
  }, [messages]);

  return (
    <div>
      {messages.map(message => <Message key={message.id} {...message} />)}
      <div ref={messagesEndRef} />
    </div>
  )
}

Also made a (very basic) codesandbox if you wanna check the behaviour https://codesandbox.io/s/scrolltobottomexample-f90lz

Solution 3 - Reactjs

Do not use findDOMNode

Class components with ref

class MyComponent extends Component {
  componentDidMount() {
    this.scrollToBottom();
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  scrollToBottom() {
    this.el.scrollIntoView({ behavior: 'smooth' });
  }

  render() {
    return <div ref={el => { this.el = el; }} />
  }
}

Function components with hooks:

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

const MyComponent = () => {
  const divRef = useRef(null);

  useEffect(() => {
    divRef.current.scrollIntoView({ behavior: 'smooth' });
  });

  return <div ref={divRef} />;
}

Solution 4 - Reactjs

Thanks to @enlitement

we should avoid using findDOMNode, we can use refs to keep track of the components

render() {
  ...

  return (
    <div>
      <div
        className="MessageList"
        ref={(div) => {
          this.messageList = div;
        }}
      >
        { messageListContent }
      </div>
    </div>
  );
}



scrollToBottom() {
  const scrollHeight = this.messageList.scrollHeight;
  const height = this.messageList.clientHeight;
  const maxScrollTop = scrollHeight - height;
  this.messageList.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
}

componentDidUpdate() {
  this.scrollToBottom();
}

reference:

Solution 5 - Reactjs

The easiest and best way I would recommend is.

My ReactJS version: 16.12.0


> ## For Class Components ##

HTML structure inside render() function

    render()
        return(
            <body>
                <div ref="messageList">
                    <div>Message 1</div>
                    <div>Message 2</div>
                    <div>Message 3</div>
                </div>
            </body>
        )
    )

scrollToBottom() function which will get reference of the element. and scroll according to scrollIntoView() function.

  scrollToBottom = () => {
    const { messageList } = this.refs;
    messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
  }

and call the above function inside componentDidMount() and componentDidUpdate()


> ## For Functional Components (Hooks) ##

Import useRef() and useEffect()

import { useEffect, useRef } from 'react'

Inside your export function, (same as calling a useState())

const messageRef = useRef();

And let's assume you have to scroll when page load,

useEffect(() => {
    if (messageRef.current) {
      messageRef.current.scrollIntoView(
        {
          behavior: 'smooth',
          block: 'end',
          inline: 'nearest'
        })
    }
  })

OR if you want it to trigger once an action performed,

useEffect(() => {
  if (messageRef.current) {
    messageRef.current.scrollIntoView(
      {
        behavior: 'smooth',
        block: 'end',
        inline: 'nearest'
      })
  }
},
[stateVariable])

And Finally, to your HTML structure

return(
    <body>
        <div ref={messageRef}> // <= The only different is we are calling a variable here
            <div>Message 1</div>
            <div>Message 2</div>
            <div>Message 3</div>
        </div>
    </body>
)

for more explanation about Element.scrollIntoView() visit developer.mozilla.org

More detailed explanation in Callback refs visit reactjs.org

Solution 6 - Reactjs

react-scrollable-feed automatically scrolls down to the latest element if the user was already at the bottom of the scrollable section. Otherwise, it will leave the user at the same position. I think this is pretty useful for chat components :)

I think the other answers here will force scroll everytime no matter where the scrollbar was. The other issue with scrollIntoView is that it will scroll the whole page if your scrollable div was not in view.

It can be used like this :

import * as React from 'react'

import ScrollableFeed from 'react-scrollable-feed'

class App extends React.Component {
  render() {
    const messages = ['Item 1', 'Item 2'];

    return (
      <ScrollableFeed>
        {messages.map((message, i) => <div key={i}>{message}</div>)}
      </ScrollableFeed>
    );
  }
}

Just make sure to have a wrapper component with a specific height or max-height

Disclaimer: I am the owner of the package

Solution 7 - Reactjs

I could not get any of below answers to work but simple js did the trick for me:

  window.scrollTo({
  top: document.body.scrollHeight,
  left: 0,
  behavior: 'smooth'
});

Solution 8 - Reactjs

If you want to do this with React Hooks, this method can be followed. For a dummy div has been placed at the bottom of the chat. useRef Hook is used here.

Hooks API Reference : https://reactjs.org/docs/hooks-reference.html#useref

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

const ChatView = ({ ...props }) => {
const el = useRef(null);

useEffect(() => {
	el.current.scrollIntoView({ block: 'end', behavior: 'smooth' });
});

 return (
   <div>
     <div className="MessageContainer" >
       <div className="MessagesList">
         {this.renderMessages()}
       </div>
       <div id={'el'} ref={el}>
       </div>
     </div>
    </div>
  );
}

Solution 9 - Reactjs

You can use refs to keep track of the components.

If you know of a way to set the ref of one individual component (the last one), please post!

Here's what I found worked for me:

class ChatContainer extends React.Component {
  render() {
    const {
      messages
    } = this.props;

    var messageBubbles = messages.map((message, idx) => (
      <MessageBubble
        key={message.id}
        message={message.body}
        ref={(ref) => this['_div' + idx] = ref}
      />
    ));

    return (
      <div>
        {messageBubbles}
      </div>
    );
  }

  componentDidMount() {
    this.handleResize();

    // Scroll to the bottom on initialization
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }

  componentDidUpdate() {
    // Scroll as new elements come along
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }
}

Solution 10 - Reactjs

I created a empty element in the end of messages, and scrolled to that element. No need of keeping track of refs.

Solution 11 - Reactjs

  1. Reference your messages container.

     <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
    
  2. Find your messages container and make its scrollTop attribute equal scrollHeight:

     scrollToBottom = () => {
         const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
         messagesContainer.scrollTop = messagesContainer.scrollHeight;
     };
    
  3. Evoke above method on componentDidMount and componentDidUpdate.

     componentDidMount() {
          this.scrollToBottom();
     }
    
     componentDidUpdate() {
          this.scrollToBottom();
     }
    

This is how I am using this in my code:

 export default class StoryView extends Component {

    constructor(props) {
        super(props);
        this.scrollToBottom = this.scrollToBottom.bind(this);
    }

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };

    componentDidMount() {
        this.scrollToBottom();
    }

    componentDidUpdate() {
        this.scrollToBottom();
    }

    render() {
        return (
            <div>
                <Grid className="storyView">
                    <Row>
                        <div className="codeView">
                            <Col md={8} mdOffset={2}>
                                <div ref={(el) => { this.messagesContainer = el; }} 
                                     className="chat">
                                    {
                                        this.props.messages.map(function (message, i) {
                                            return (
                                                <div key={i}>
                                                    <div className="bubble" >
                                                        {message.body}
                                                    </div>
                                                </div>
                                            );
                                        }, this)
                                    }
                                </div>
                            </Col>
                        </div>
                    </Row>
                </Grid>
            </div>
        );
    }
}

Solution 12 - Reactjs

There are two major problems with the scrollIntoView(...) approach in the top answers:

  1. it's semantically incorrect, as it causes the entire page to scroll if your parent element is scrolled outside the window boundaries. The browser literally scrolls anything it needs to in getting the element visible.

  2. in a functional component using useEffect(), you get unreliable results, at least in Chrome 96.0.4665.45. useEffect() gets called too soon on page reload and the scroll doesn't happen. Delaying scrollIntoView with setTimeout(..., 0) fixes it for page reload, but not first load in a fresh tab, at least for me. shrugs

Here's the solution I've been using, it's solid and is more compatible with older browsers:

function Chat() {
   const chatParent = useRef<HTMLDivElement(null);

   useEffect(() => {
      const domNode = chatParent.current;
      if (domNode) {
         domNode.scrollTop = domNode.scrollHeight;
      }
   })
   return (
      <div ref={chatParent}>
         ...
      </div>
   )
}

Solution 13 - Reactjs

Working Example:

You can use the DOM scrollIntoView method to make a component visible in the view.

For this, while rendering the component just give a reference ID for the DOM element using ref attribute. Then use the method scrollIntoView on componentDidMount life cycle. I am just putting a working sample code for this solution. The following is a component rendering each time a message received. You should write code/methods for rendering this component.

class ChatMessage extends Component {
	scrollToBottom = (ref) => {
		this.refs[ref].scrollIntoView({ behavior: "smooth" });
	}

	componentDidMount() {
		this.scrollToBottom(this.props.message.MessageId);
	}

    render() {
        return(
            <div ref={this.props.message.MessageId}>
                <div>Message content here...</div>
            </div>
        );
    }
}

Here this.props.message.MessageId is the unique ID of the particular chat message passed as props

Solution 14 - Reactjs

import React, {Component} from 'react';

export default class ChatOutPut extends Component {

    constructor(props) {
        super(props);
        this.state = {
            messages: props.chatmessages
        };
    }
    componentDidUpdate = (previousProps, previousState) => {
        if (this.refs.chatoutput != null) {
            this.refs.chatoutput.scrollTop = this.refs.chatoutput.scrollHeight;
        }
    }
    renderMessage(data) {
        return (
            <div key={data.key}>
                {data.message}
            </div>
        );
    }
    render() {
        return (
            <div ref='chatoutput' className={classes.chatoutputcontainer}>
                {this.state.messages.map(this.renderMessage, this)}
            </div>
        );
    }
}

Solution 15 - Reactjs

thank you 'metakermit' for his good answer, but I think we can make it a bit better, for scroll to bottom, we should use this:

scrollToBottom = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}

but if you want to scroll top, you should use this:

scrollToTop = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}   

and this codes are common:

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}


render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}

Solution 16 - Reactjs

As another option it is worth looking at react scroll component.

Solution 17 - Reactjs

I like doing it the following way.

componentDidUpdate(prevProps, prevState){
  this.scrollToBottom();
}

scrollToBottom() {
  const {thing} = this.refs;
  thing.scrollTop = thing.scrollHeight - thing.clientHeight;
}

render(){
  return(
    <div ref={`thing`}>
      <ManyThings things={}>
    </div>
  )
}

Solution 18 - Reactjs

This is how you would solve this in TypeScript (using the ref to a targeted element where you scroll to):

class Chat extends Component <TextChatPropsType, TextChatStateType> {
  private scrollTarget = React.createRef<HTMLDivElement>();
  componentDidMount() {
    this.scrollToBottom();//scroll to bottom on mount
  }

  componentDidUpdate() {
    this.scrollToBottom();//scroll to bottom when new message was added
  }

  scrollToBottom = () => {
    const node: HTMLDivElement | null = this.scrollTarget.current; //get the element via ref

    if (node) { //current ref can be null, so we have to check
        node.scrollIntoView({behavior: 'smooth'}); //scroll to the targeted element
    }
  };

  render <div>
    {message.map((m: Message) => <ChatMessage key={`chat--${m.id}`} message={m}/>}
     <div ref={this.scrollTarget} data-explanation="This is where we scroll to"></div>
   </div>
}

For more information about using ref with React and Typescript you can find a great article here.

Solution 19 - Reactjs

This works for me

messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight

where const messagesEndRef = useRef(); to use

Solution 20 - Reactjs

Using React.createRef()

class MessageBox extends Component {
        constructor(props) {
            super(props)
            this.boxRef = React.createRef()
        }
        
        scrollToBottom = () => {
            this.boxRef.current.scrollTop = this.boxRef.current.scrollHeight
        }
        
        componentDidUpdate = () => {
            this.scrollToBottom()
        }
        
        render() {
            return (
                        <div ref={this.boxRef}></div>
                    )
        }
}

Solution 21 - Reactjs

This is modified from an answer above to support 'children' instead of a data array.

Note: The use of styled-components is of no importance to the solution.

import {useEffect, useRef} from "react";
import React from "react";
import styled from "styled-components";

export interface Props {
    children: Array<any> | any,
}

export function AutoScrollList(props: Props) {
    const bottomRef: any = useRef();

    const scrollToBottom = () => {
        bottomRef.current.scrollIntoView({
            behavior: "smooth",
            block: "start",
        });
    };

    useEffect(() => {
        scrollToBottom()
    }, [props.children])

    return (
        <Container {...props}>
            <div key={'child'}>{props.children}</div>
            <div key={'dummy'} ref={bottomRef}/>
        </Container>
    );
}

const Container = styled.div``;

Solution 22 - Reactjs

In order to scroll down to the bottom of the page first we have to select an id which resides at the bottom of the page. Then we can use the document.getElementById to select the id and scroll down using scrollIntoView(). Please refer the below code.

   scrollToBottom= async ()=>{
      document.getElementById('bottomID').scrollIntoView();
    }

Solution 23 - Reactjs

Full version (Typescript):

import * as React from 'react'

export class DivWithScrollHere extends React.Component<any, any> {

  loading:any = React.createRef();

  componentDidMount() {
    this.loading.scrollIntoView(false);
  }

  render() {

    return (
      <div ref={e => { this.loading = e; }}> <LoadingTile /> </div>
    )
  }
}

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
QuestionhelsontView Question on Stackoverflow
Solution 1 - ReactjsmetakermitView Answer on Stackoverflow
Solution 2 - ReactjsDiego LaraView Answer on Stackoverflow
Solution 3 - ReactjstgdnView Answer on Stackoverflow
Solution 4 - Reactjsjk2KView Answer on Stackoverflow
Solution 5 - ReactjsAhamed RasheedView Answer on Stackoverflow
Solution 6 - ReactjsGabriel BourgaultView Answer on Stackoverflow
Solution 7 - ReactjsrilarView Answer on Stackoverflow
Solution 8 - ReactjsChathurika SenaniView Answer on Stackoverflow
Solution 9 - ReactjshelsontView Answer on Stackoverflow
Solution 10 - ReactjsTushar AgarwalView Answer on Stackoverflow
Solution 11 - ReactjsMarcin RapaczView Answer on Stackoverflow
Solution 12 - ReactjsKai ArnoldView Answer on Stackoverflow
Solution 13 - ReactjsSherin JoseView Answer on Stackoverflow
Solution 14 - ReactjsPavan GarreView Answer on Stackoverflow
Solution 15 - ReactjsAbolfazl MiadianView Answer on Stackoverflow
Solution 16 - ReactjsJohnView Answer on Stackoverflow
Solution 17 - ReactjsaestrroView Answer on Stackoverflow
Solution 18 - ReactjsDaniel BudickView Answer on Stackoverflow
Solution 19 - ReactjsktaView Answer on Stackoverflow
Solution 20 - ReactjsakashView Answer on Stackoverflow
Solution 21 - ReactjsguymanView Answer on Stackoverflow
Solution 22 - ReactjsSenthuranView Answer on Stackoverflow
Solution 23 - ReactjsTechTurtleView Answer on Stackoverflow