I've built a modal with React Bootstrap that generally works fine. But when the browser is zoomed in, an inner div scrolls down a little, obscuring the top of the modal. So I added a useEffect to scroll back to the top when the modal is loaded. I was able to accomplish this in my functional React component like this:
// A reference to a `div` at the top of my modal
// Note: This approach won't work: const titleRef = useRef(null);
const titleRef = React.createRef();
useEffect(() => {
if (titleRef.current) {
const modalBodyContainer = document.getElementById('modalBodyContainer');
if (modalBodyContainer) {
modalBodyContainer.scrollTop = 0;
}
}
}, [titleRef]);
I need to add some more code so it's only run once, when first loaded but it otherwise does work correctly.
But I'm wondering if there's another way than using a ref in the way I have?
Related
I have problem with scroll to top after rerender component.
Is there any way to prevent this ? I dont want to scroll to top after any rerender just change the content.
This may not be the best way of doing it in React, but it should work.
As described in this page, there is a way to get the amount of pixels the user scrolled and store them in a variable. Then use that value to programmatically change the amount of scrolled pixels after the rerender. This is for Javascript, but it should also work for React.
Simple Example:
// Get the scroll pixels from the top of the page (Store this before rerender)
const scrollFromTop = document.documentElement.scrollTop;
// Update the scroll pixels from the top of the page with the stored int value (Do this after rerender)
document.documentElement.scrollTop = scrollFromTop;
you can give your component a state,
import React, {useState, useEffect} from 'react';
const MyComponent = props => {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
if (scrolled) {
return; // no more scrolling
}
setScrolled(true);
// TODO: scroll to top here
}, [scrolled]);
// ...
};
export default MyComponent;
I have modal screen (using react-bootstrap), on modal screen i have multiple overlays (popup menus) linked to items. These overlays has inputs, and when i click on input it immediately loses focus. I cant figure out whats wrong, because another one popup menu, that i have on normal screen, not modal, works fine. Tried to set autofocus, but it immediately loses too.
I wrote example, https://codesandbox.io/s/rkemy
I think it is somehow connected with popper, because bootstrap overlay uses it, dont know where to dig
The fix provided in the other response is a workaround that doesn't fix the real cause of the issue.
The issue is caused by an internal logic of the Modal component of react-overlay library that is a dependency library of react-bootstrap.
Specifically, the issue is caused by code listed below
const handleEnforceFocus = useEventCallback(() => {
if (!enforceFocus || !isMounted() || !modal.isTopModal()) {
return;
}
const currentActiveElement = activeElement();
if (
modal.dialog &&
currentActiveElement &&
!contains(modal.dialog, currentActiveElement)
) {
modal.dialog.focus();
}
});
that enforce the focus on the first modal open as soon as that modal lose the focus, like when you move the focus on the input.
In order to solve the issue, you have to pass the enforceFocus={false} to your Modal component.
The documentation of the API can be found here: https://react-bootstrap.github.io/react-overlays/api/Modal#enforceFocus
As the docs says:
Generally this should never be set to false as it makes the Modal less accessible to assistive technologies, like screen readers. but in your scenario this is a need to work properly.
Solution is to wrap Overlay in container:
import React from "react";
import { Overlay } from "react-bootstrap";
import { X } from "react-bootstrap-icons";
export const PopupMenuWrapper = (props) => {
const { target, title, show, onClose, children } = props;
const ref = React.useRef(null);
return (
<div ref={ref}>
<Overlay
container = {ref.current}
target={target.current}
show={show}
placement="bottom-start"
rootClose={true}
onHide={onClose}
>
...
</div>
...
I am using this snippet of code and passing handleDownload method in download button. Ref is also properly passed in barChart. That works, but page is really slowed down by rerendering and reloading canvas all the time.
I used this
example.
How can I avoid this issue?
const [png, ref] = useRechartToPng()
const handleDownload = useCallback(async () => {
FileSaver.saveAs(png, "graph.png")
}, [png])
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
Sandbox: https://codesandbox.io/s/falling-brook-de970
Packages: React Spectrum, Tailwind Transition Component
The Problem: The useOverlayPosition users two refs, a triggerRef and an overlayRef to determine the absolute positioning that should be applied to the popup.
Without transitions, there are no issues with positioning.
When I add the Transition component, I notice that the overlayRef isn't set once rendered and therefore doesn't allow the useOverlayPosition hook to determine the right positioning props to apply. I assume this is because the state.isOpen is false and the children of the Transition component aren't rendered yet.
A subsequent press while open fixes the issue.
Clicking off the button continues the issue.
Clicking on the button after it's open will fix the issue, but I don't understand why.
The issue was resolved using a callbackRef instead of useRef. This allowed the component to re-render when the ref was assigned.
In a useRef, updating ref.current does not trigger a re-render but with a callbackRef it will.
Idea taken from: https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
const [overlayRef, setOverlayRef] = React.useState<
React.RefObject<HTMLElement>
>({ current: null });
const callbackRef = React.useCallback((node) => {
if (node !== null) {
setOverlayRef({ current: node });
}
}, []);