How to pass data in react js inside a file - reactjs

I have the next component in ReactJs:
function App() {
function handleClick(e) {
console.log('click ', e);
localStorage.setItem('my-item', e.key);
console.log(localStorage.getItem('my-item'))
}
return (
<div>
<Menu
selectedKeys={e.key}
style={{ width: 500px}}
onClick={handleClick}
>
...another code
}
There i used AntDesign. The function handleClick write in my localstorage the key of an item. It works, but i want to pass in selectedKeys the key that i passed in the localstorage, so i want to do this selectedKeys={e.key}, but i can't do this. How to pass the data e.key from the function to<Menu> tag?

What about using a state :
With the React hook useState :
import React, { useState } from React;
then
const [selectedKeys, setSelectedKeys] = useState([]);
then
function handleClick(key) {
localStorage.setItem('my-item', key);
let newKeys = selectedKeys.slice(0); // you may need to clone the array
newKeys.push(key);
setSelectedKeys(newKeys); // or push it into an array if there is many.
}
your Menu component would then look like :
<Menu
mode="inline"
selectedKeys={selectedKeys}
openKeys={openKeys}
onOpenChange={onOpenChange}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>
All together :
import React, { useState } from React;
const App = () => {
const [selectedKeys, setSelectedKeys] = useState([]);
const handleClick = (key) => {
localStorage.setItem('my-item', key);
let newKeys = selectedKeys.slice(0); // you may need to clone the array
newKeys.push(key);
setSelectedKeys(newKeys); // or push it into an array if there is many.
}
return {
<Menu
mode="inline"
selectedKeys={selectedKeys}
openKeys={openKeys}
onOpenChange={onOpenChange}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>
}
}

Make it a class component and in the state make an attribute of selected key and in the handleClick do set the state and assign the selectedKey value from the state in the Menu tag. Thats the proper way of doing it.
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
selectedKey: null
};
}
handleClick = (e) => {
console.log('click ', e);
this.setState({
selectedKey: e.key
});
localStorage.setItem('my-item', e.key);
console.log(localStorage.getItem('my-item'));
}
render () {
return (
<div>
<Menu
selectedKeys={this.state.selectedKey}
style={{ width: 500px}}
onClick={handleClick}
>
...another code
}
}

Use an arrow function:
onClick={() => handleClick(e.key)}
In your code:
handleClick = key => {
console.log("the key is:", key);
localStorage.setItem('my-item', key);
console.log(localStorage.getItem('my-item'))
}
<Menu
selectedKeys={e.key}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>

Related

Child component not re rendering after Parent state changes

I have a RadioGroup component that contains multiple RadioButton components. Here's the code for the RadioGroup component:
const RadioGroup = ({radioGroupData}) => {
const [radioGroupRefreshData, setRadioGroupRefreshData] = useState(radioGroupData);
const handleClick = (index) => {
setRadioGroupRefreshData(radioGroupRefreshData.map((obj, i) => {
if(i !== index) {
return {text: obj.text, isSelected: false};
}
return {text: obj.text, isSelected: true};
}));
};
return (
<View style={styles.container}>
{
radioGroupRefreshData.map((obj, i) => {
return <RadioButton index={i}
text={obj.text}
isSelected={obj.isSelected}
onClick={handleClick} />
})
}
</View>
);
}
The RadioGroup component has a state variable (an array) called radioGroupRefreshData. when each RadioButton is defined inside the RadioGroup, the handleClick function is passed as a prop in order to be called when a RadioButton is clicked. Here is the code for the RadioButton component:
const RadioButton = (props) => {
const [isSelected, setIsSelected] = useState(props.isSelected);
const initialRenderDone = useRef(false);
useEffect(() => {
if(!initialRenderDone.current) {
initialRenderDone.current = true;
}
else {
props.onClick(props.index);
}
}, [isSelected]);
const handlePress = () => {
if(!isSelected) {
setIsSelected(true);
}
}
return (
<TouchableOpacity style={styles.outsideContainer} onPress={handlePress}>
<View style={styles.radioButtonContainer}>
{ (isSelected) && <RadioButtonInnerIcon width={15} height={15} fill="#04004C" /> }
</View>
<Text style={styles.radioButtonText}>{props.text}</Text>
</TouchableOpacity>
);
}
From what I know, each RadioButton component should re render when the Parent's variable radioGroupRefreshData changes, but the RadioButton component's are not re rendering.
Thank you in advance for any help that you can give me!
Since you have a state in RadioButton you need to update it when the props change. So in RadioButton add useEffect like this:
useEffect(() => {
setIsSelected(props.isSelected);
},[props.isSelected]);
Also you don't have to mix controlled and uncontrolled behaviour of the component: do not set RadioButton state inside RadioButton since it comes from the RadioGroup

if else statement not working in react component

I am trying to implement a condition in my react component . When the user triggers the onClick the state updates allStakes creating one array of 4 values. The problem is that I do not want the user to input more than 4 values so tried to give the limit by doing an if else statement. I tried to add a console.log in both statements.The weird fact is that setState get updated but the csonole.log is never displayed.The component keeps rendering all the values that I insert even if the array is longer than 4. Thanks in advance
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: []
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
const { allStakes } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({ allStakes: [allStakes, ...newStake] })
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}
/>
</div>
</li>
))
}
</ul>
</div>
May be it happens because of incorrect array destructuring. Try to change this code:
this.setState({ allStakes: [allStakes, ...newStake] })
by the next one:
this.setState({ allStakes: [newStake, ...allStakes] })
Your state belongs to your FetchRandomBet component and you are trying to update that from your imported component. There are 2 solutions to that.
1> Wrap your Stake component to a separate component with onClick handler something like this
<div onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({
allStakes: [allStakes, ...newStake
]
})
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}><Stake /></div>
Or
2> Pass the state as a prop to the Stake component which will be responsible to update the state for FetchRandomBet. something like this
<Stake parentState={this}/>
And inside the Stake component change the parentState on click of wherever you want.
I solved the problem. I transfered the onClick method in stake component and I handled the upload of the common array with an array useState. I add the value to newStake and when I click ok I retrieve newStake and spread it into a new array and then I check that array. If there is a value should not keep adding otherwise it can add values. It works fine. Thanks anyway
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake(props) {
const [newStake, setStake] = useState(null);
const [allStakes, setStakes] = useState(null);
const changeStake = (e) => {
setStake([e.target.value])
}
const mySubmit = () => {
if (!allStakes) {
setStakes([...newStake, allStakes])
props.onClick(newStake);
} else if (allStakes) {
console.log('stop')
}
}
return (
<>
<CurrencyInput
onChange={changeStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
<button onClick={mySubmit}>yes</button>
<button>no</button>
{newStake}
</>
);
}
export default Stake;

StencilJS emit event to React

I have a nested set of StencilJS components. I would like to attach a function to my nested component so that my React app, which hosts the parent component, can read.
Example
<pw-actionbar
actions={getActions}
/>
In this actionbar component, I have another nested button component. It looks like this
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
==> I WANT A FUNCTION ON PW-BUTTON THAT PASSES 'action' which my react app reads
return <pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? 'selected' : 'notselected'}
class="displace"
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}/>
</i>
</pw-button-side-menu>
})
}
</div>
</Host>
);
}
My react app has some component
functionEmittedFromPwButton(action) {
console.log(action) <=== I WANT THIS TO WORK IN MY REACT APP WHICH IS EMITTED FROM THE PW-BUTTON COMPONENT NESTED IN THE PW-ACTIONBAR COMPONENT
}
return (
<MyComponent>
<pw-actionbar actions={getActions}/> <=== that takes an array of objects. I want to capture the 'action' object emitted by the pw-button nested in this component in my react app
</MyComponent>
)
I have tried all sorts of different methods like this one to try to emit the object from stencil to react
On the stenciljs side
import { Component, h, Host, Prop, Event, EventEmitter } from "#stencil/core";
#Component({
tag: "pw-actionbar",
styleUrl: "pw-actionbar.scss",
shadow: true,
})
export class PwActionbar {
#Prop() actions: any = [];
#Prop() iconfillcolor: "white" | "black" = "white";
#Prop() iconstrokecolor: "white" | "black" = "white";
#Event() emitAction: EventEmitter;
render() {
const handleClick = (action) => {
this.emitAction.emit(action);
};
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
// cast the button
return (
<pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? "selected" : "notselected"}
class="displace button-lines"
onClick={() => handleClick(action)}
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}
/>
</i>
</pw-button-side-menu>
);
})
}
</div>
</Host>
);
}
}
On the react side
const handleAction = async (action, history, i18n) => {
Metrics.track("Changed Page", { action });
if ("sign-out" === action) {
await authActions.logout();
history.push(`/${i18n.locale}`);
} else if ("help-desk" === action) {
history.push(`/${i18n.locale}/zendesk`);
} else if ("advisors" === action) {
pageActionsObjAdmin[0].onpage = true;
history.push(`/${i18n.locale}/admin/advisors`);
} else if ("users" === action) {
pageActionsObjAdmin[1].onpage = true;
history.push(`/${i18n.locale}/admin/users`);
} else if ("forecast" === action) {
pageActionsObjAdmin[3].onpage = true;
history.push(`/${i18n.locale}/admin/forecast`);
} else if ("stats" === action) {
pageActionsObjAdmin[4].onpage = true;
history.push(`/${i18n.locale}/admin/stats`);
}
};
const Layout = ({ children }) => {
const { i18n } = useLingui();
const [, setContext] = useContext(StripeErrorContext);
const history = useHistory();
useEffect(() => {
const listener = (e) => {
// set page button to be "Active"
pageActionsObjAdmin.forEach((element) => {
element.onpage = false;
});
handleAction(e.detail.page, history, i18n, setContext);
};
// listen for events emitted form the action bar
document.body.addEventListener("emitAction", listener);
return () => {
document.body.removeEventListener("emitAction", listener);
};
}, []); // eslint-disable-line
// refs for the actionbar
const elementRef = useRef(null);
useEffect(() => {
if (elementRef.current !== null) {
elementRef.current.actions = pageActionsObjAdmin;
}
}, [elementRef]);
return (
<Wrapper>
<Header />
<BodyLayout>
<pw-actionbar
ref={(el) => (elementRef.current = el)}
style={{ paddingTop: "56px", zIndex: "99" }}
class="action-bar"
/>
<div className="main-layout" style={{ width: "100%" }}>
{children}
</div>
</BodyLayout>
</Wrapper>
);
};
export default Layout;

How to set a state that is on a component?

On reactjs, how can I setNotify again a state that is on a component?
import React, { useState } from 'react'
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
// if (props.message === "") {
// props.message = "Some Error"
// }
// if (props.message !== "") {
// setNotify(false)
// }
// if (props) {
// const [notify] = useState(true)
// }
console.log("notify.state:", props)
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}
export default NotificationError
If I use the following:
if (props) {
const [notify] = useState(true)
}
I get the error,
Line 17:26: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
If I use the following
if (props.message !== "") {
setNotify(true)
}
It throws the following...
Error: Too many re-renders. React limits the number of renders to
prevent an infinite loop.
Simply, I am not understanding this. Can you please help? :(
Rewrite you logic to something like:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if (props.message === "") {
props.setMessage('Some Error');
}
setNotify(false);
}, [props.message]);
return (
<div
className="notification is-danger"
style={notify ? { display: "none" } : { display: "block" }}
>
<button className="delete" onClick={() => setNotify(true)}></button>
Error: {props.message}
</div>
);
};
Props are immutable so if you want to change a message you should pass a callback.
Also, take a read about Rules of Hooks.
Use a useEffect hook for such cases. It works similar to componentDidMount and componentDidUpdate in class component. It means the function that you pass as the first argument of useEffect hook triggers the first time when your component mounts and then every time any of the elements of the array changes that you pass as the second argument.
Here is the code example:
const NotificationError = (props) => {
const [notify, setNotify] = useState(false);
useEffect(() => {
if(props.message != '') {
setNotify(false);
}
}, [props.message])
const closeNotification = (e) => {
console.log("Should be closing notification")
setNotify(e)
}
return (
<div className="notification is-danger" style={notify ? {display: 'none'} : {display: 'block'}}>
<button className="delete" onClick={() => closeNotification(true)}></button>
Error: {props.message}
</div>
)
}

How to scroll to bottom in react?

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?
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.
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
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} />;
}
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:
https://facebook.github.io/react/docs/react-dom.html#finddomnode
https://www.pubnub.com/blog/2016-06-28-reactjs-chat-app-infinite-scroll-history-using-redux/
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
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
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'
});
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>
);
}
There are two major problems with the scrollIntoView(...) approach in the top answers:
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.
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>
)
}
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();
}
}
}
Reference your messages container.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
Find your messages container and make its scrollTop attribute equal scrollHeight:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
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>
);
}
}
I created a empty element in the end of messages, and scrolled to that element. No need of keeping track of refs.
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
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>
);
}
}
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>
);
}
As another option it is worth looking at react scroll component.
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>
)
}
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.
This works for me
messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight
where const messagesEndRef = useRef(); to use
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>
)
}
}
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``;
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();
}
I have face this problem in mweb/web.All the solution is good in this page but all the solution is not working while using android chrome browser .
So for mweb and web I got the solution with some minor fixes.
import { createRef, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'redux/store';
import Message from '../Message/Message';
import styles from './MessageList.module.scss';
const MessageList = () => {
const messagesEndRef: any = createRef();
const { messages } = useSelector((state: AppState) => state?.video);
const scrollToBottom = () => {
//this is not working in mWeb
// messagesEndRef.current.scrollIntoView({
// behavior: 'smooth',
// block: 'end',
// inline: 'nearest',
// });
const scroll =
messagesEndRef.current.scrollHeight -
messagesEndRef.current.clientHeight;
messagesEndRef.current.scrollTo(0, scroll);
};
useEffect(() => {
if (messages.length > 3) {
scrollToBottom();
}
}, [messages]);
return (
<section className={styles.footerTopSection} ref={messagesEndRef} >
{messages?.map((message: any) => (
<Message key={message.id} {...message} />
))}
</section>
);
};
export default MessageList;
This is a great usecase for useLayoutEffect as taught by Kent C. Dodds.
https://kentcdodds.com/blog/useeffect-vs-uselayouteffect
if your effect is mutating the DOM (via a DOM node ref) and the DOM mutation will change the appearance of the DOM node between the time that it is rendered and your effect mutates it, then you don't want to use useEffect.
In my case i was dynamically generating elements at the bottom of a div so i had to add a small timeout.
const bottomRef = useRef<null | HTMLDivElement>(null);
useLayoutEffect(() => {
setTimeout(function () {
if (bottomRef.current) bottomRef.current.scrollTop = bottomRef.current.scrollHeight;
}, 10);
}, [transactionsAmount]);
const scrollingBottom = () => {
const e = ref;
e.current?.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "start",
});
};
useEffect(() => {
scrollingBottom();
});
<span ref={ref}>{item.body.content}</span>
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>
)
}
}

Resources