I'm working on a chat app and am using the scroller from bottom to top to load older messages.
When a new message arrives I want to check first if the user is at the bottom of the div, and only then use a scrollToBottom function.
How can I get the current height/position of the user?
https://www.npmjs.com/package/react-infinite-scroller
Thank you,
Omri
Unfortunately it's been a few days without reply. This is my workaround:
I created a boolean called isBottom, and attached onScroll={handleScroll} function to my messages div.
const [isBottom, setIsBottom] = useState(true);
const scrollToBottom = (behavior) => {
messagesEndRef.current.scrollIntoView();
};
const handleScroll = (e) => {
const bottom =
e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
if (bottom) {
setIsBottom(true);
} else {
setIsBottom(false);
}
};
The messages div:
<div className="msg_list" onScroll={handleScroll}>
<InfiniteScroll
loadMore={loadMore}
initialLoad={true}
hasMore={hasMoreItems}
loader={<LoadingAnimation key={0} />}
useWindow={false}
isReverse={true}
>
{messages}
<div ref={messagesEndRef} />
</InfiniteScroll>
</div>
And then I added a useEffect to handle changes from my messages array (arriving from props)
useEffect(() => {
if (isBottom) {
scrollToBottom();
} else {
setUnreadMessages((unreadMessages) => unreadMessages + 1);
}
}, [messageList]);
* BTW you also need to wire the scrollTobottom function to your send message box, since if you are the one who sent the message it should scrollToBottom anyway
Related
I am trying to have the animation work everytime i click on the buttons in the below project
Code - https://codesandbox.io/s/8fjs8i
Description
There are 3 buttons named first, second and third. when i click on first, the purple box below shows first, when i click on second it shows second and so on, basically the div is updating everytime based on button click.
Problem : but the animation fadIn that i have given to the div works only on application load. how can i make it work everytime i click the button so the the box fadesIN with animation for every click.
const Renders = ({ arr }) => {
const [load, setLoad] = useState(false);
useEffect(() => {
setLoad(true);
setTimeout(() => {
setLoad(false);
}, 1);
}, [arr]);
if (load) return <></>;
return (
<div className="renders">
<div className="zoomers">{arr}</div>
</div>
);
};
This another way you can do it:
const Renders = ({ arr }) => {
const [load, setLoad] = useState(false);
useEffect(() => {
setLoad(true);
setTimeout(() => {
setLoad(false);
}, 1);
}, [arr]);
return (
<div className="renders">
<div className={load ? '' : "zoomers"}>{arr}</div>
</div>
);
};
Now look the different, before you load and unload, now its just a play with the class name, the idea that every time you load element with the animation class so its start to work
I've seen tutorials that demonstrates how to do this but all that I've seen uses "window" or examples where the element comes in view from the bottom of a page. I have a modal with a fixed height that scrolls:
const ItemMarkup = forwardRef(({user}, ref) => <li ref={ref}>...</li>)
// This opens up in a modal: https://headlessui.dev/react/dialog
<div className="content">
{/** Tailwind's fixed height 'h-96' */}
<ul ref={scrollRef} className="h-96 overflow-scroll">
{users.map((user, index) =>
<ItemMarkup
{/** I'm looking for the last item/element to come in this view (ul) */}
ref={users.length === index + 1 ? ref : null}
key={user.id}
user={user}
/>
)}
</ul>
</div>
I've looked at this demo but again, it's for the entire page/window. Then I've tried using this function:
// element would be the ref.current
// target would be scrollRef
// But this always return false
function isInViewport(element, target) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= target.innerHeight &&
rect.right <= target.innerWidth
);
}
Here's how I tried the attempt:
const ref = useRef()
const scrollRef = useRef()
// From the demo link above
const onScreen = useOnScreen(ref)
// No idea if I need this
const [scrolling, setScrolling] = useState(false)
useEffect(() => {
const onScroll = e => {
setScrolling(true)
console.log(isInViewport(ref.current, scrollRef))
};
scrollRef?.current.addEventListener("scroll", onScroll);
//console.log(ref, onScreen)
return () => scrollRef?.current.removeEventListener("scroll", onScroll);
}, [ref, onScreen, scrolling])
This seems all wrong. Is there a react hook I could to determine when a specific element comes in a modal view or any scrollable view that I specify and not the document view? I just cannot find any documentation/tutorials on this.
I'm creating an infinite scroll. When the last items comes in view, of the modal, I then trigger a function to fetch more data.
I guess I have over complicated things:
useEffect(() => {
const onScroll = e => {
console.log(isInViewport(e.target))
};
scrollRef?.current.addEventListener("scroll", onScroll);
return () => scrollRef?.current.removeEventListener("scroll", onScroll);
}, [])
function isInViewport(element) {
return element.scrollHeight - element.scrollTop === element.clientHeight
}
Now when I scroll to the bottom, I see a console log of true.
I have an Infinite Scroll with React.js
It is literally Infinite, so I can't measure How many is it.
<div className="InfiniteScroll">
<div ref={observer} className="item">item</div>
<div ref={observer2} className="item">item</div>
{/* Actually I am using map() */}
{...}
</div>
I can add IntersectionObserver on any div.
const observer = new IntersectionObserver(() => {
console.log('Found it');
},{
root: divRef.current,
rootMargin: ...,
});
When I want to know the currently visible divs, How can I know that with this?
Add IntersectionObserver on each div looks not reasonable.
If I want to invoke some function with 1,000th div or random div, how can I achieve it?
you can set any div with code
new IntersectionObserver(function(div) {
div.isIntersecting && do_something();
}).observe(div);
I would suggest you attach a ref to the very last div. So when that last div is intersecting, then you can invoke your function there.
Attaching ref to the last div
{list.map((item, index) => {
if (item.length === index + 1) {
return <div ref={lastItemElementRef} key={item}>{item}</div>
} else {
return <div key={item}>{item}</div>
}
})}
Now when you reach this last div you can have an external API call for infinite scroll and invoke some function
Intersecting logic
const observer = useRef()
const lastItemElementRef = useCallback(node => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
// logic for triggering set state and API call - Infinite scroll
// some other function invokation
}
})
if (node) observer.current.observe(node)
}, [loading, otherdependencies])
I want to conditionally render a custom notification component (not 100% sure how it works, it was written by others and it uses material-ui). I want this notification to appear every time a button gets clicked, but for some reason the notification component will stop re-appearing after it auto-hides itself after some time, even when I click the re-click the button - again, I'm not totally sure how this works.
I just want to know if there's a way to dismount and then re-mount this custom notification component so that it gets newly rendered on each button click (is that bad practice?). This is a simplified ver. of my current logic:
const RandComponent = ()=>{
const [notifType, setNotifType] = useState(1);
const toggleNotif = ()=>{
setNotifType(!notifType);
}
const getNotif = ()=>{
return <Notification type={notifType}/>
}
return (<>
....
<button onClick={toggleNotif}>TOGGLE</button>
{getNotif(notifType)}
</>)
}
You can do it like this
const RandComponent = () => {
const [showNotification,setShowNotification] = useState(true):
const toggleNotificationHandler = () => {
setShowNotification(p => !p);
}
return (<>
<button onClick={toggleNotificationHandler}>TOGGLE</button>
{showNotification && <Notification />}
</>)
}
I'm building a conference website using three of these tabs (#speaker, #talks, #schedule). I think it is fair to want interactions between the tabs, here are a couple use cases that I cannot seem to solve.
From the #talks tab, I click on the bio hash - #johnsmith. This id exists within the page, but since I don't first switch tab to #speakers, nothing renders.
If I want to reference a specific talk and email someone the url: https://website.com#speaker_name the tabs won't open, and nothing but the tabs render.
The problem is compounded by the fact that when I click on an anchor tag href using a '#id', I must reload the page for it to fire.
I feel like there should be some way to pass a parameter when changing the tab or something... I'm in a tough spot because I'm rolling out code, but need this functionality badly.
Here is the actual open-source repo - https://github.com/kernelcon/website. The code I'm referencing can be found in src/pages/Agenda/.
Here is some example code.
Agenda.js
<Tabs defaultTab={this.state.defaultTab}
onChange={(tabId) => { this.changeTab(tabId) }}
vertical={vert}>
<TabList vertical>
<Tab tabFor="speakers">Speakers</Tab>
<Tab tabFor="talks">Talks</Tab>
<span>
<TabPanel tabId="speakers">
<Speakers />
</TabPanel>
<TabPanel tabId="talks">
<Talks />
</TabPanel>
</span>
</Tabs>
Talks.js
changeTab(id) {
window.location.reload(false);
}
getTalks() {
// Order Alphabetically
const talksOrdered = speakerConfig.sort((a,b) => (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0));
const talks = talksOrdered.map((ele, idx) => {
const twitterUrl = ele.twitter.replace('#', '');
return (
<div id={ele.talk_id}
key={idx}
className='single-talk'>
<div className='talk-title'>{ele.title}</div>
<div className='talk-sub-title'>
<div className='speaker-name'>
<a onClick={() => {this.changeTab(ele.speaker_id)}}
href={`#${ele.speaker_id}`}>{ele.speaker}</a>
</div>
...
I ended up accomplishing this by sending #tab_title/speaker_name, then adding a componentWillMount lifecycle method and function in the main tab file like below.
componentWillMount() {
const defaultTab = this.props.location.hash ? this.props.location.hash.split('#')[1] : 'schedule';
this.setState({
defaultTab: defaultTab
});
this.handleHashChange();
window.addEventListener('hashchange', this.handleHashChange);
}
handleHashChange = () => {
// given `#speakers/dave` now you have tabName='speakers', speakerHash='dave'
const [tabName, speakerHash] = window.location.hash.replace('#', '').split('/');
const tabNamesToWatchFor = [
'schedule',
'speakers'
];
if (tabNamesToWatchFor.includes(tabName)) {
this.setState({
defaultTab: tabName,
// pass this.state.speakerHash to <Speakers/> and use this for scrollIntoView in componentDidMount
speakerHash: speakerHash
});
}
}
Next, I went to the individual tab (in this case Speakers.js) and added a componentDidMount and componentDidUpdate method to help scroll to the speaker itself.
componentDidMount() {
this.handleScrollToSpeaker(this.props.speakerHash);
}
componentDidUpdate(prevProps) {
if (prevProps.speakerHash !== this.props.speakerHash) {
this.handleScrollToSpeaker(this.props.speakerHash);
}
}
handleScrollToSpeaker = hash => {
window.setTimeout(() => {
const ele = document.querySelector(`#${hash}`);
if (ele) {
ele.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
}, 500)
}