Trap non interactive element's focus - reactjs

I have an non interactive element like a div on the page that serves as a loading modal. When it is loading, I want to show this modal as well as the rest of the page containing interactive elements with a opacity of 50%. When it is loading(loading modal shows up), I want the focus trapped in this modal and make it so the user cannot tab through the rest of the elements on the page. I've tried the code below by adding the ReactFocusLock around the div and give the div a tabIndex of -1 so it is focusable and set it to be focusable at the beginning but it does not work. However, if I put a link or something interactable inside the div, it works well, which means the ReactFocusLock is fine. Could you please help?
const MyComponent = () => {
const {loading, data} = fetchData();
return <>
{loading && <LoadingModal />}
{data && <div>data</div>}
<input></input>
<button></button>
<>;
}
// original modal
const LoadingModal = () => {
return <div>loading data....<div>;
}
// modal that with ReactFocusLock from react-focus-lock
const LoadingModal = () => {
const ref = React.useRef(null);
React.useEffect(() => {
ref.current.focus();
}, []);
return <ReactFocusLock><div ref={ref} tabIndex={-1}>loading data....</div></ReactFocusLock>;
}

Related

How to get the width of an element using ref in react

I'm trying to get the width of an element, and to do this I'm using the following...
export default () => {
const { sidebarOpen } = useContext(AuthContext)
const containerRef = useRef()
const getWidth = () => myRef.current.getBoundingClientRect().width
const [width, setWidth] = useState(0)
console.log(sidebarOpen)
useEffect(() => {
const handleResize = () => setWidth(getWidth())
if(myRef.current) setWidth(getWidth())
window.addEventListener("resize", handleResize)
console.log('abc', myRef.current.offsetWidth)
return () => window.removeEventListener("resize", handleResize)
}, [myRef, sidebarOpen])
console.log(width)
return (
<div ref={containerRef}>
...
</div>
)
}
When the width of the screen is changed in dev tools page resize, it works fine, but when the value of sidebar changes from 'true' to 'false' (or vice-versa). It returns the previous value of the container width. Does anyone know what I'm doing wrong, I need to get the most up to date value of the container width every time it changes, whether this is caused by a change to 'sidebarOpen' or not.
By default, a div element takes the full width of its parent.
I guess you're using flexbox for the sidebar, so when it closes, the element that contains the content (not the sidebar) expands to the full width of the available space.
This might be the issue that you're facing.
The div with flexbox (on the left we have the Sidebar component and on the right the content):
And right now, when there is no Sidebar:
As another example, let's create a div that has no other property rather than backgroundColor:
Eg:
<div style={{backgroundColor: 'red'}}>Hello</div>
And the result:

React-Bootstrap Modals cause problems for window EventListener

I have built a React app which uses React-Bootstrap Modals.
In my return() function I have button which onClick changes state and shows/hides a div element.
const [showInfo, setShowInfo] = useState(false);
const toggleInfo = React.useCallback(() => {
setShowInfo(!showInfo);
}, [showInfo]);
useEffect(() => {
if (showInfo) {
document.addEventListener("click", toggleInfo);
return () => {
document.removeEventListener("click", toggleInfo);
};
}
}, [showInfo, toggleInfo]);
return (
...
<Button onClick={() => toggleInfo()}>
...
)
After loading the page, pressing the button changes the state and the div element is shown/hidden depending on the state. If I click anywhere on the window it hides the div element.
Everything works fine until I open any Modal dialog.
After that, when I click my button that shows/hides div the document.addEventListener("click", toggleInfo) and document.removeEventListener("click", toggleInfo) execute immediately one after the other and the div element does not get displayed.
If I reload the page, it works again and I made sure that this problem occurs only after opening the Modal dialog.
Any help or tips would be greatly appreciated
Fixed the issue by adding e.stopPropagation() to the toggleInfo() function:
const toggleInfo = React.useCallback(
(e) => {
e.stopPropagation();
setShowInfo(!showInfo);
},
[showInfo]
);
return (
...
<Button onClick={(e) => toggleInfo(e)}>
...
)

Is there any alternative to the used method 'scroll top' when a link is clicked?

When a user clicks a link that directs the user to a new page, it generally put the user's view in the middle of the new page, at the same position as the original link. To prevent this, we can use the well-known method; scrolling up from window events.
I would like to know if there are any alternatives or tips that will prevent the user from seeing the scrolling up. Ideally, I would like the view to be at the top straight away like a new open page.
Thank you,
I found the following solution in my case to behave like a new page:
const ParentComponent: React.FC = () => {
const [isViewTopPage, setViewTopPage] = useState(false);
const scrollToTheTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
let result = window.scrollY === 0;
setViewTopPage(result);
};
useEffect(() => {
// If the user refresh the page
if (window.scrollY === 0) {
setViewTopPage(true);
}
// User coming from the link clicked
if (!isViewTopPage) {
window.addEventListener('scroll', scrollToTheTop);
}
return () => window.removeEventListener('scroll',
scrollToTheTop);
}, [isViewTopPage]);
return (
<section id="parent-component">
{/* Your conditional rendering *}
{isViewTopPage && (
<Header/>
<YourChildComponent/>
<Footer/>
)}
</section>
);
};
Note: as my child component was not too down from the top viewport, the scrolling up was very fast. It might not be the case for you if your child component is too down. You might have to implement an animation while scrolling up for the user experience.

clickOutside hook triggers on inside select

I have a card component which consists of 2 selects and a button, select1 is always shown and select2 is invisible until you press the button changing the state. I also have an onClickOutside hook that reverts the state and hides select2 when you click outside the card.
The problem Im having is that in the case when select2 is visible, if you use any select and click on an option it registers as a click outside the card and hides select2, how can I fix this?
Heres the relevant code from my card component:
const divRef = useRef() as React.MutableRefObject<HTMLInputElement>;
const [disableSelect2, setDisableSelect2] = useState(true);
const handleActionButtonClick = () => {
setDisableSelect2(!disableSelect2)
}
useOutsideClick(divRef, () => {
if (!disableSelect2) {
setDisableSelect2(!disableSelect2);
}
});
return (
<div ref={divRef}>
<Card>
<Select1>[options]</Select1>
!disableSelect2 ?
<Select2>[options]</Select2>
: null
<div
className="d-c_r_action-button"
onClick={handleActionButtonClick}
>
</Card>
</div>
);
};
And this is my useoutsideClick hook
const useOutsideClick = (ref:React.MutableRefObject<HTMLInputElement>, callback:any) => {
const handleClick = (e:any) => {
if (ref.current && !ref.current.contains(e.target)) {
callback();
}
};
useEffect(() => {
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
});
};
Extra informtaion: Im using customized antd components and cant use MaterialUI
I tried to recreate your case from the code you shared. But the version I 'built' works.
Perhaps you can make it fail by adding in other special features from your case and then raise the issue again, or perhaps you could use the working code from there to fix yours?
See the draft of your problem I made at https://codesandbox.io/s/serverless-dust-njw0f?file=/src/Component.tsx

How to set focus to input after the element stops moving?

In React a have a search bar which shows up after I click "Open" button. Search bar appears smoothly because I use translateX property. Then I would like to use focus() on the input element in this search bar. I tried to use refs. Focus appears but it appears as soon as I press the button and stays all the way the search bar moves from one part of the screen to the other(rememeber, it because I use translateX). So it all rides before my eyes. How to make focus appers after the search bar 'reached the destination' on the screen? I tried
if (props.visibleSearchBar) {
ReactDOM.findDOMNode(inputRef.current).focus()
}
instead of
if (props.visibleSearchBar) {
inputRef.current.focus();
} // same story
const Search = (props) => {
let inputRef = useRef(null);
if (props.visibleSearchBar) {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} />
</div >
)
You can disable the input initially, and enable it after the transition ends.
something like this, (it is not tested).
const [disabled, setDisabledState] = useState(true)
useEffect(() => {
const callback = () => {
setDisableState(false)
inputRef.current.focus();
}
inputRef.current.addEventListener('transitionend', callback)
return () => inputRef.current.removeEventListener('transitioned', callback)
}, [])
return (
<div>
<input ref={inputRef} disabled={disabled}/>
</div >
)

Resources