react-headroom - momentarily disable when animating scroll from another component - reactjs

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 🍻

Related

Forceful Re-render with useEffect

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?

Update state for multiple components sharing a state

I have these multiple button components and one state isYes. I want to handle the states such that clicking on one button component will trigger applyMethod and the state for all buttons will be toggled. (i.e for button which was clicked + other buttons whose state was previously set to true)
That is: only one button can have isYes as true at a time. Others should be set back to false. Only one button can have "Hello" text, others should be to default "Bye"
Right now, it only toggles state for the button that was clicked.
const [isYes, setIsYes] = useState(false);
const applyMethod = () => {
setYes(!isYes);
};
<Button onClick={applyMethod}>
<div>
isYes
? 'Hello'
: 'Bye'}
</div>
</Button>
You can use different state for each component, I don't think it is a good idea to use same state for multiple components (buttons). You can use an array to store states for different components and depending on each button click, you can update that and other button states accordingly.
I've taken 10 button as example in this case:
import React from "react";
export default function App() {
const btnCount = 10;
const btnsIntialState = new Array(btnCount);
btnsIntialState.fill(false);
btnsIntialState[0] = true;
const [btn, setBtn] = React.useState(btnsIntialState);
console.log(btnsIntialState);
const applyMethod = (index, value) => {
const btnCopy = [...btn];
btnCopy.fill(value);
btnCopy[index] = !value;
setBtn(btnCopy);
};
return (
<div className="App">
{btn.map((value, i) => (
<button onClick={() => applyMethod(i, value)} key={i}>
{value ? "Hello" : "Bye"}
</button>
))}
</div>
);
}
You can do a couple things, first thing you need to look at is your statrmanagemen pattern. Redux is a great way to pass data to multiple components.Theres a little setup but the rest of your code will be lighter and with react-redux hooks any component can access the data..
Now if your looking for an easier fix, you can setup views in your app. This is a component that basically handles all the state for this part a the app or feature. Then you can pass all props or state you like to your buttons

What's the best way to detect in which div am i to change the style of a navbar in React?

I'm making a single page project and i want to add a border bottom color in my navbar when the page is in the div that the navbar takes you using react-scroll. What's the best way to detect in what div am i? My idea was that somehow i can have a state that is the place where the user is in the page and then the matching element in my navbar has a border color to represent where you are.
I tried using Intersection Observer but i't doesn't feel like the best way for it, for example i have divs that are pretty big and some that are small and in a page it can be 3 at the same time and intersection observer.isVisible works really weird.
For example if i'm in the logo div in the page to add a cool border to the Logo i in my navbar.
If i'm in the second section add a border to the second section text in my nav.
Using is intersecting the state updates but sometimes it updates to false after i'm way out of the div.
import React, { useRef, useEffect, useState } from "react";
import "../../App.css";
const Second = () => {
const ref = useRef();
const [isVisible, setIsVisible] = useState();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
console.log(entry.isVisible);
setIsVisible(entry.isVisible);
});
observer.observe(ref.current);
}, []);
return <div id="second" ref={ref}></div>;
};
export default Second;
The second div looks like this:
Even where the react-scroll takes me isIntersecting is equal to false, and using the same but with isVisible is false 24/7.
You can set an id for each div. And change the links of the navbar as #services. So that you can detect the location of the window by window.location. If that is equal to the nav link you can add a dynamic class to that link. I think this is an easy way to do this without intersectionObserver.
<nav>
<ul>
<li className={`navlink ${window.location === '/#services' && 'navlink-active'}`}>Services</>
...
</ul>
</nav>

Input fields inside react overlays doesnt work in modal

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>
...

Restore scroll position after toggled a modal in react with useContext

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);

Resources