Goal
Restore the scoll position of the window after the a div has toggled from position fixed to none.
Problem
Restoring the scroll position doesnt work although i get it saved correctly to the state.
Description
I have a page where a modal is opened via onClick. Therefore i created a "ToggleModalContext" to pass the props to the modal on the one hand and to the background div on the other hand. I want to modify the background div with setting the css property position to fixed to avoid that the background is scrolled instead of the content of the modal.
When the modal is closed, the position: fixed is removed and i want to restore the scroll position of the window.
This last step doesnt work. Maybe someone else has an idea?
ToggleModalContext (Thats the context, where the scroll restore function is called)
import React from "react";
export const ToggleModalContext = React.createContext();
export const ModalProvider = props => {
const [toggle, setToggle] = React.useState(false);
const [scrollPosition, setScrollPosition] = React.useState();
function handleToggle() {
if (toggle === false) {
setScrollPosition(window.pageYOffset); // When the Modal gets opened, the scrollposition is saved correctly
}
if (toggle === true) {
window.scrollTo(0, scrollPosition); // Restoring doesnt work.
}
setToggle(!toggle);
}
return (
<ToggleModalContext.Provider value={[toggle, handleToggle]}>
{props.children}
</ToggleModalContext.Provider>
);
};
Maybe somebody has a idea?
Maybe i have to use useEffect? But how?
Thanks for your time in advance :)
From the description you have provided, you are using a fixed position on the background div to remove scrolling on the window when you open your modal
On the other hand, you are calling
if (toggle === true) { window.scrollTo(0, scrollPosition);}
before your modal has closed. At this time, the background div is in a fixed position and there is no where to scroll to.
You need to ensure that your modal has safely closed and your background div is back to its normal position before calling this function. To see the behavior, you can use a setTimeout function and call this function there with a set time e.g.
setTimeout(() => window.scrollTo(0, scrollPosition), 2000);
Related
import * as React from "react";
// import "./style.css";
export default function App() {
let [width, setWidth] = React.useState(window.innerWidth);
let [height, setHeight] = React.useState(window.innerHeight);
React.useEffect(() => {
console.log("useEffect is called");
window.addEventListener("resize", () => {
setHeight(window.innerHeight);
setWidth(window.innerWidth);
});
}, []);
return (
<div>
{/* <button onClick={handler}> Submit </button> */}
<h1>
{" "}
{height},{width}{" "}
</h1>
</div>
);
}
The above code causes re-render of height and width values on the UI (height =windows.innerHeight & width = windows.innerWidth) despite using useEffect with an empty dependency array.
I've deployed useState inside useEffect to update height and width. My understanding was that useEffect gets executed only once(after the initial render) if used with an empty dependency array but on resizing the screen size, height and width gets updated as well thereby causing re-render
you should use const to declare your states
window.addEventListener is declared only once but it will be triggered every resize - so your state will be updated every resize
every time your state change the component will rerender
The useEffect is called only once, but since you have added a listener on the window object using addEventListener. The code inside the handler (which sets the state) gets executed, whenever there window size is changed. This state update will cause your component to re-render and update the UI
I think that's because you added addEventListener for resizing window in useEffect, So whenever window in resized it's callback function will be called and your states will be changed so your component will be re-rendered
the window.addEventListener method runs just one time and then you have a listener for resize and its call back excuted. the window.addEventListener does'nt execute on each resize, it's callback does, ist it clear?
I have have a tabs-component that becomes sticky when a user scrolls past it's scroll position on the page. When a tab is clicked it will scroll the user up or down, depending on where their current scroll position is, in relation to the related tab-content's scroll position.
Is it possible to momentarily disable/reactivate the react-headroom functionality from another component, when required?
Ideally, when scroll-up is initiated via these tabs, I wish to trigger the react-headroom hide-header functionality, if the header is already shown, or disable the show-header functionality, if the header is already hidden. Any suggestions how one would achieve this?
Thanks in advance.
I ended up resolving this by storing a boolean value of false in useState, to control the toggling of the react-headroom's disable prop. As the scrollIntoView animation is based on time, not distance travelled, so you can rely 😅 on setting a setTimeout of 500ms to reset the the useRef value back to its original state.
When I want to trigger a scroll and/or disable the header hide/show functionality, I place a useRef on the scroll-trigger, which an onClick event calls a function. Within this function, I set useState to true (to activate the disable prop & disable the header), animate the page, then use a setTimeout of 500ms to reset the useRef value back it original state, which will re-activate the header functionality.
import { useRef, useState } from 'react';
import Headroom from 'react-headroom';
const SiteHeader = (): JSX.Element => {
const [headroomDisabled, setHeadroomDisabled] = useState(false);
const myRef = useRef<HTMLDivElement>(null);
const myRef2 = useRef<HTMLDivElement>(null);
const executeScroll = () => myRef.current?.scrollIntoView();
const executeScrollUp = () => {
setHeadroomDisabled(true);
myRef2.current?.scrollIntoView();
setTimeout(() => {
setHeadroomDisabled(true);
}, 500);
};
return (
<>
<Headroom disable={headroomDisabled}>
<h2>Test header content</h2>
</Headroom>
<div ref={myRef2}>Element to scroll to</div>
<button onClick={executeScroll}> Click to scroll </button>
<div style={{ marginTop: '150vh' }}></div>
<div ref={myRef}>Element to scroll to</div>
<button onClick={executeScrollUp}> Click to scroll </button>
</>
);
};
export default SiteHeader;
I ended up taking this a step further and placing this logic in some global state, as so any component could utilise this functionality 🍻
I have a created popover menu, and I am trying to add the functionality of displaying this popover menu on the first visit to a page with the component. My idea was to use useEffect() and localStorage, however it seems that changes made to my usePopover() state do not seem to be saving.
const { showed, toggle, hide, show } = usePopover()
useEffect(() => {
if(!localStorage["alreadyVisited"]) {
localStorage["alreadyVisited"] = true;
show
}
}, [show])
Edit: The solution ended up being that I needed to change show to show()
You are not saving nor calling the storage , this is how should be done :
useEffect(() => {
if(!localStorage.getItem("alreadyVisited")) {
localStorage.setItem("alreadyVisited",true)
show
}
}, [show])
use this as a ref https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
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>
...