Scroll div to bottom when rendered React - reactjs

I have a div of Messages in my chat application written in React. I want the div to be scrolled to the bottom when the user loads the page for the first time in order to see the latest messages. How can I implement this?

you can use useLayoutEffect hook to see when your div is rendered and use a refrence to your div element to scrollit to end
const Messages = () => {
const divRef = useRef();
...
useLayoutEffect(() => {
if (divRef.current)
divRef.current.scrollTop = divRef.current.scrollHeight;
}, [divRef]);
...
return (
<div ref={divRef}>
...
</div>
);
};

You just need to write useEffect hook either at the start when the component renders or when all the messages are fetched. Something like this
React.useEffect(() => {
window.scrollTo(0,document.body.scrollHeight);
}, []);
or
React.useEffect(() => {
window.scrollTo(0,document.body.scrollHeight);
}, [messages]);
Depends on how you're components are structured or how the views are managed. But this is the outline how you can trigger scrollTo when a user goes to your message screen.

Related

Why would useRef object be null even after attaching to a div element?

Hey yall Im having a weird and annoying issue while trying to useRef on a div element. I have this working exactly as it is on another page but this doesnt seem to be doing what I want it to on this page.
Im trying to implement and endless scroll. The goal is to attach the ref (refetchTrigger) to a certain div on the page and have that trigger a fetch for the next page of data when scrolled into view. It seems to render the div correctly but refetchTrigger is not updated to be the div, it just remains null. Seems like a rerender needs to happen here but obviously changes to refs dont trigger a rerender. Ive been battling with this all morning and would greatly appreciate any suggestions. In the code snippet below, all console.log(refetchTrigger.current) are printing out null.
Its also worth noting that the refetch call is using useSWR hook to fetch data. when removing this the attaching of ref to div seemed to work correctly. also, when adding a button to fetch its fetching as expected. its just trying when trying to automatically trigger the fetch that Im seeing the issue.
Thanks for the help!
export const TrackGrid = () => {
const [list, setList] = useState<Track[]>([]);
const [page, setPage] = useState<number>(1);
const refetchTrigger = useRef<HTMLDivElement | null>(null);
const inViewport = useIntersection(refetchTrigger, "0px");
const { tracks, error, isValidating } = useGetTracks(false, page, 20);
useEffect(() => {
if (inViewport) {
setPage(page + 1);
}
console.log("in viewport");
}, [inViewport]);
useEffect(() => {
if (tracks) setList([...list, ...tracks]);
}, [tracks]);
const renderDiv = () => {
console.log(refetchTrigger.current);
const d = <div ref={refetchTrigger}>exists</div>;
console.log(refetchTrigger.current);
return d;
};
return (
<>
<div className="grid place-items-center grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{!!list.length && renderDiv()}
{list.map((track: Track, i: number) => {
console.log(refetchTrigger.current);
return (
<div ref={refetchTrigger} key={i}>
<TrackGridItem track={track} />
</div>
);
})}
</div>
</>
);
};
Here is the code thats interacting with the ref
```export const useIntersection = (element: any, rootMargin: string) => {
const [isVisible, setState] = useState<boolean>(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setState(entry.isIntersecting);
},
{ rootMargin }
);
element.current && observer.observe(element.current);
return () => element.current && observer.unobserve(element.current);
}, []);
return isVisible;
};```
The ref only gets populated after the render is complete, and the element has been created by react on the actual dom. Logging the ref out during the render will not work (unless there has been a previous render).
The fix is to put your code that needs to interact with the ref into a useEffect. That way by the time your code runs, the render is complete and the element is on the page.
useEffect(() => {
console.log(refetchTrigger.current)
}, []);
Essentially the ref will not be populated until after the whole render pass is finished. This is not obvious to many React programmers (and it usually doesn't matter) but the DOM is not actually committed until later on. When you call renderDiv() and pass the ref, on the first mount the element is not even rendered in the DOM at the stage that code executes. React effectively executes and renders the virtual DOM tree into the real DOM shortly after the render pass.
If you have code that is reliant on the DOM node existing, because you need to read something for whatever reason you need to run it an effect or (this is something you have to be careful with), run it in a ref callback.
The fix for me was updating the useEffect to remove the dependency array. According to the new react docs, the recommended read or write example is in a useEffect without a dependency array (runs every time).
In my above example the ref is being used in the useIntersection hook. I removed the dependency array and it worked as expected

React component switch animation with a loader - Glitchy

I have a requirement where i have couple of components out of which only one could be displayed at a particular point of time. The components are of varying heights.
When a user clicks on the button in the currently displayed component i need to
Show a loader to the user
Render the component in background.
Wait till the rendered component calls onLoad callback.
Once onLoad callback is recived, hide the loader.
Something like below (Code sandbox here)
const [loading, setLoading] = useState(false);
const [activeElement, setActiveElement] = useState("Component1");
const onLoad = () => {
setLoading(false);
};
const onClick = () => {
setLoading(true);
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
};
return (
<div className="container">
<ViewWrapperHOC loading={loading}>
{activeElement === "Component1" ? (
<Component1 onLoad={onLoad} onClick={onClick} />
) : (
<Component2 onLoad={onLoad} onClick={onClick} />
)}
</ViewWrapperHOC>
</div>
);
I was planning to write a wrapper component (ViewWrapperHOC in above example) to show the transition between the components and show the loader. Now the problem is when i try to animate, since i have to render both the progressbar and children in viewwrapper, the animation seems to be very glitchy.
Code sandbox here
Could someone suggest a way to fix this. I am open to any alternate animations or any alternate approach to achieve this in any form of pleasant ux. Thanks in advance.
In that second code sandbox, your issue is that as soon as you click, you are changing which element is active, therefore it gets rendered.
You could put a small timeout in the onClick function:
const onClick = () => {
setLoading(true);
setTimeout(() => {
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
}, 100);
};
Then in view wrapper you'll need to get rid of transition: "200ms all ease-in-out, 50ms transform ease-in-out".
You need to have a known height here to be able to make a smooth change in height transition.
See codesandbox fork
Version 2:
Here is version 2, using refs. Main changes are moving the box shadow out of the components to the container in view wrapper. Adding refs to the components, passing the active ref to the wrapper and then setting the height with height transition.
To be honest, I'd probably restructure the code a lot if I had the time.

React useEffect strange behaviour with custom layout component

I'm trying to use scroll position for my animations in my web portfolio. Since this portfolio use nextJS I can't rely on the window object, plus I'm using navigation wide slider so I'm not actually scrolling in the window but in a layout component called Page.
import React, { useEffect } from 'react';
import './page.css';
const Page = ({ children }) => {
useEffect(() => {
const scrollX = document.getElementsByClassName('page')
const scrollElement = scrollX[0];
console.log(scrollX.length)
console.log(scrollX)
scrollElement.addEventListener("scroll", function () {
console.log(scrollX[0].scrollTop)
});
return () => {
scrollElement.removeEventListener("scroll", () => { console.log('listener removed') })
}
}, [])
return <div className="page">{children}</div>;
};
export default Page;
Here is a production build : https://next-portfolio-kwn0390ih.vercel.app/
At loading, there is only one Page component in DOM.
The behaviour is as follow :
first listener is added at first Page mount, when navigating, listener is also added along with a new Page component in DOM.
as long as you navigate between the two pages, no new listener/page is added
if navigating to a third page, listener is then removed when the old Page is dismounted and a new listener for the third page is added when third page is mounted (etc...)
Problem is : when you navigate from first to second, everything looks fine, but if you go back to the first page you'll notice the console is logging the scrollX value of the second listener instead of the first. Each time you go on the second page it seems to add another listener to the same scrollElement even though it's not the same Page component.
How can I do this ? I'm guessing the two component are trying to access the same scrollElement somewhat :/
Thanks for your time.
Cool site. We don't have complete info, but I suspect there's an issue with trying to use document.getElementsByClassName('page')[0]. When you go to page 2, the log for scrollX gives an HTMLCollection with 2 elements. So there's an issue with which one is being targeted. I would consider using a refs instead. Like this:
import React, { useEffect, useRef } from 'react';
import './page.css';
const Page = ({ children }) => {
const pageRef = useRef(null)
const scrollListener = () => {
console.log(pageRef.current.scrollTop)
}
useEffect(() => {
pageRef.addEventListener("scroll", scrollListener );
return () => {
pageRef.removeEventListener("scroll", scrollListener )
}
}, [])
return <div ref={pageRef}>{children}</div>;
};
export default Page;
This is a lot cleaner and I think will reduce confusion between components about what dom element is being referenced for each scroll listener. As far as the third page goes, your scrollX is still logging the same HTMLElement collection, with 2 elements. According to your pattern, there should be 3. (Though there should really only be 1!) So something is not rendering properly on page 3.
If we see more code, it might uncover the error as being something else. If refs dont solve it, can you post how Page is implemented in the larger scope of things?
also, remove "junior" from the "junior developer" title - you won't regret it

React settimeout in useeEffect is called every time I navigate back

I'm trying to change the style of an element within setTimeout and useEffect.
Here is how the code looks like
In home.js:
useEffect(()=>{
setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
},1000)
// changeStyles for 'i love frontend'
changeStyles2()
},[])
I found that after the home page is rendered if I navigate to other pages and come back to home page, changesStyles1() will be called again and animation will run again. What should I do to avoid calling setTimeout() again when I navigate back to home page from other pages.
You will notices that changeStyle2 won't be animated again if I navigate from home page to other page and then back to home page. And this is the effect I want.
My website is hosted on netlify
My code on codepen
I would manage it from the parent component.
Lets assume the following component tree:
App
Home
Page1
In App:
const [isFirstMount, setFirstMount] = useState(true);
//...
return(
<Home ...props changeStyle={isFirstMount} onStyleChanged={() =>
setFirstMount(false)}/>
//...
)
In Home:
if (props.changeStyle) {
let pnt = setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
onStyleChanged()
clearTimeout(pnt);
}, 1000)
}
You need to clear the timer, otherwise, you may experience adverse side effects in your code.
useEffect(()=>{
const timer = setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
}, 1000)
// changeStyles for 'i love frontend'
changeStyles2()
//clear the timer
return () => clearTimeout(timer);
},[]);
see this link for more info.

React Ant Design Modal Method update on state change

I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs

Resources