Respuestas:
Como mencionó Tushar, puedes mantener un div ficticio en la parte inferior de tu 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>
);
}
y luego desplácese hasta él cada vez que se actualice su componente (es decir, estado actualizado a medida que se agregan nuevos mensajes):
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
Estoy usando el método estándar Element.scrollIntoView aquí.
this.messagesEnd.scrollIntoView()
funcionó bien para mí. No hubo necesidad de usar findDOMNode()
.
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}
que funcione en versiones más nuevas
Solo quiero actualizar la respuesta para que coincida con el nuevo React.createRef()
método , pero es básicamente lo mismo, solo tenga en cuenta la current
propiedad en la referencia creada:
class Messages extends React.Component {
const messagesEndRef = React.createRef()
componentDidMount () {
this.scrollToBottom()
}
componentDidUpdate () {
this.scrollToBottom()
}
scrollToBottom = () => {
this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' })
}
render () {
const { messages } = this.props
return (
<div>
{messages.map(message => <Message key={message.id} {...message} />)}
<div ref={this.messagesEndRef} />
</div>
)
}
}
ACTUALIZAR:
Ahora que los ganchos están disponibles, estoy actualizando la respuesta para agregar el uso de los ganchos useRef
y useEffect
, lo real que hace la magia (las referencias de React y el scrollIntoView
método DOM) sigue siendo el mismo:
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>
)
}
También hizo un codeandbox (muy básico) si desea verificar el comportamiento https://codesandbox.io/s/scrolltobottomexample-f90lz
this.messagesEnd.current
siempre existe. Sin embargo, es importante notar que llamar this.messagesEnd.current
antes del primer render resultará en el error que señaló. Thnx.
this.messagesEnd
en su primer ejemplo en el método scrollTo?
useEffect
método debe colocarse con () => {scrollToBottom()}
. Muchas gracias de todos modos
No utilice findDOMNode
class MyComponent extends Component {
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
scrollToBottom() {
this.el.scrollIntoView({ behavior: 'smooth' });
}
render() {
return <div ref={el => { this.el = el; }} />
}
}
import React, { useRef, useEffect } from 'react';
const MyComponent = () => {
const divRref = useRef(null);
useEffect(() => {
divRef.current.scrollIntoView({ behavior: 'smooth' });
});
return <div ref={divRef} />;
}
behavior
(no se puede editar porque "las ediciones deben tener al menos 6 caracteres", suspiro).
scrollIntoView
con smooth
es muy pobre en este momento.
Gracias a @enlitement
debemos evitar usar findDOMNode
, podemos usar refs
para realizar un seguimiento de los componentes
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();
}
referencia:
Puedes usar ref
s para realizar un seguimiento de los componentes.
Si conoce una forma de establecer el valor ref
de un componente individual (el último), ¡publíquelo!
Esto es lo que encontré que funcionó para mí:
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();
}
}
}
react-scrollable-feed se desplaza automáticamente hacia abajo hasta el último elemento si el usuario ya estaba en la parte inferior de la sección desplazable. De lo contrario, dejará al usuario en la misma posición. Creo que esto es bastante útil para los componentes de chat :)
Creo que las otras respuestas aquí forzarán el desplazamiento cada vez, sin importar dónde esté la barra de desplazamiento. El otro problema scrollIntoView
es que se desplazará por toda la página si su div desplazable no estaba a la vista.
Se puede usar así:
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>
);
}
}
Sólo asegúrese de tener un componente contenedor con una específica height
omax-height
Descargo de responsabilidad: soy el propietario del paquete
Haga referencia a su contenedor de mensajes.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
Encuentra tu contenedor de mensajes y haz que su scrollTop
atributo sea igual scrollHeight
:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
Evocar el método anterior en componentDidMount
y componentDidUpdate
.
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
Así es como estoy usando esto en mi código:
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>
);
}
}
Creé un elemento vacío al final de los mensajes y me desplacé hasta ese elemento. No es necesario realizar un seguimiento de las referencias.
Si desea hacer esto con React Hooks, puede seguir este método. Porque se ha colocado un div ficticio en la parte inferior del chat. useRef Hook se utiliza aquí.
Referencia de la API de Hooks: 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>
);
}
Mi versión de ReactJS: 16.12.0
Estructura HTML dentro de la render()
función
render()
return(
<body>
<div ref="messageList">
<div>Message 1</div>
<div>Message 2</div>
<div>Message 3</div>
</div>
</body>
)
)
scrollToBottom()
función que obtendrá la referencia del elemento. y desplácese según la scrollIntoView()
función.
scrollToBottom = () => {
const { messageList } = this.refs;
messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
}
y llama a la función anterior dentro componentDidMount()
ycomponentDidUpdate()
para obtener más explicaciones sobre la Element.scrollIntoView()
visita developer.mozilla.org
Ejemplo de trabajo:
Puede utilizar el scrollIntoView
método DOM para hacer que un componente sea visible en la vista.
Para esto, mientras renderiza el componente, simplemente proporcione una ID de referencia para el elemento DOM usando el ref
atributo. Luego usa el método scrollIntoView
del componentDidMount
ciclo de vida. Solo estoy poniendo un código de muestra funcional para esta solución. El siguiente es un componente que se representa cada vez que se recibe un mensaje. Debe escribir código / métodos para renderizar este componente.
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>
);
}
}
Aquí this.props.message.MessageId
está el ID único del mensaje de chat en particular pasado comoprops
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>
);
}
}
Me gusta hacerlo de la siguiente manera.
componentDidUpdate(prevProps, prevState){
this.scrollToBottom();
}
scrollToBottom() {
const {thing} = this.refs;
thing.scrollTop = thing.scrollHeight - thing.clientHeight;
}
render(){
return(
<div ref={`thing`}>
<ManyThings things={}>
</div>
)
}
gracias 'metakermit' por su buena respuesta, pero creo que podemos hacerlo un poco mejor, para desplazarse hacia abajo, deberíamos usar esto:
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}
pero si desea desplazarse hacia arriba, debe usar esto:
scrollToTop = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}
y estos códigos son comunes:
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>
);
}
Como otra opción, vale la pena mirar el componente de desplazamiento de reacción .
Utilizando 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>
)
}
}
Así es como resolvería esto en TypeScript (usando la referencia a un elemento de destino al que se desplaza):
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>
}
Para obtener más información sobre el uso de ref con React y Typescript, puede encontrar un excelente artículo aquí .
Versión completa (mecanografiado):
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>
)
}
}
Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'.
y Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'.
así ...