React: scrollIntoView on componentDidMount using refs - reactjs

I am rendering a Dialog component with a lot of text and the dialog has therefore a scroll. Text contains some links, and the content of the Dialog changes when a link is clicked. The problem is, when the content changes, the scroll stays in the same position at the height of the link. I would like to scroll to the top of the dialog when the new content is displayed.
I have created a class component for the Dialog content and implemented the handleTop function that uses refs and scrollIntoView to scroll to the top of the Dialog. When this function is called from a Button click event (TestButton), then the content is scrolled to the top. However when this function is called from the componentDidMount(), nothing happens.
export default class DialogContent extends React.Component {
constructor(props) {
...
this.myRef=React.createRef();
}
handleTop = () => {
this.myRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
};
componentDidMount() {
this.handleTop();
}
render() {
return (
<div>
<h1 ref={this.myRef}>Title</h1>
<p>A lot of text</p>
<Button>TestButton</Button>
<div>
)
}
I also verified that the componentDidMount function gets called by placing a console.log message.
Any ideas or suggestions would be kindly appreciated.

Related

How to scroll to the top of an MS Fluent UI modal using react

I've created an SPFX webpart which displays a large modal. On smaller screens it will show the bottom half of the modal if scrolled down. I want the modal to have a button at the bottom so the user can click it to scroll to the top.
I've read multiple conversations about how to scroll a window to the top but how to, with a Modal?
I am using the below to try to assign a modal to a variable (outside the class).
const modal = React.createRef();
export default class Eia extends React.Component<IEiaProps, any> {
constructor(props) {
super(props);
///
I've tried using this from the documentation:
export default class Eia extends React.Component<IEiaProps, any> {
constructor(props) {
super(props);
//#ts-ignore
this.myRef = React.createRef();
this.state = {
But I have to force an ignore from TypeScript.
Here's the modal I'm using:
<Modal
titleAriaId={this._titleId}
subtitleAriaId={this._subtitleId}
isOpen={this.state.showModal}
onDismiss={this._closeModal}
closeButtonAriaLabel={"Close"}
isBlocking={true}
containerClassName={styles.modalBody}
//#ts-ignore
ref={this.myRef}
>
I suppose my question is, how to target the modal instead of the window and then force it to scroll to the top on a button press or automatically each render?
I've read this: How to set modal scroll to top when it appears in React.js
But is it really necessary to have to create a ref?
There is no way to do this using refs as far as I can tell, so I added an id to the top most div and ``` to allow the user to jump to the top. Happy to be wrong about the refs.

How to scroll to a Ref within a modal scree in react

I currently have multiple components within a bootstrap modal. My goal is to be able to do a window.scroll to a given component following a user action. Basically, this is what I have tried:
class App extends Component {
constructor (props) {
super(props);
this.myref = React.createRef();
}
// function that I have been trying to invoke.
scrollToRef = () => {
window.scrollTo({top: this.myref.current.offsetTop, behavior: "smooth"})
}
render () {
return (
<Modal>
<ComponentOne/>
<ComponentTwo/>
<ComponentThree ref={this.myref}/>
</Modal>
)
}
}
All of my components are class components. I even tried wrapping ComponentThree in a div tag if that made a difference, but no luck. Any pointers would be greatly appreciated, Thank you!
window.scrollTo would pertain to the window object, therefore will attempt to scroll the window, not the <Modal> component. For this, you can have another ref attached to the <Modal>, and that is the element you would use scrollTo on. Since <ComponentThree> is already a direct child of <Modal>, you can continue using the offsetTop property but take note:
offsetTop is the number of pixels from the top of the closest
relatively positioned parent element

Add close button to custom HTML InfoBox of Bing Maps in React project

There is an example of "Custom HTML InfoBox" for Bing Maps. Ironically, there is no close button in the customized InfoBox. Every InfoBox(InfoWindow) of other map APIs (Google Map, MapBox, ) has a default close button with customized HTML content except the one of Bing Map API. It is hard to close an InfoBox without a close button.
So, I have to add a close button into the customized HTML InfoBox. The following is the pseudo code I'm writing. I'm using React + Next.js.
// MyInfoBox.js
export class MyInfoBox extends Component {
constructor(props) {
super(props);
const { map, pos, events, children } = props;
const htmlContent = renderToStaticMarkup(
<div>
<button onClick={events.close}>✖</button>
{children}
</div>
);
const infoBox = new Microsoft.Maps.InfoBox(
pos,
htmlContent,
);
infoBox.setMap(map);
}
render() {
return null;
}
}
// index.js
export default () => <MyInfoBox events={{ close: () => console.log('close') }} />;
InfoBox takes only HTML for the content parameter (why not DOM element?). Unfortunately, renderToStaticMarkup (or renderToString) renders every properties but 'onClick' to HTML. So there is no reaction when we click the close button.
How to write an available close button for custom HTML InfoBox of Bing Map in React project?
After the investigation for several days, I find a very tricky solution. We have to register an event handler to InfoBox and detect the original source of the event. If it is the close button, invoke the corresponding callback.
Microsoft.Maps.Events.addHandler(this.#infoWindow, 'click', e => {
if (e.originalEvent.target.id === closeButtonId) {
events.close(e);
}
});
It is too tricky. We'd prefer that InfoBox of Bing Maps provides the default close button (with event hooking).

How to handle onClick state change when an outside click handler "blocks" it?

The context:
I am using the following outside click handler: https://github.com/airbnb/react-outside-click-handler
I have a standard sidebar and a hamburger button which shows/hides the sidebar when clicked. When I wrap my sidebar component with the outside click handler as per the docs everything works fine as I click anywhere on the canvas and it hides my sidebar.
//App.js
<OutsideClickHandler onOutsideClick={this.hideSideBar}>
<SideBar show={this.state.sideBarOpen}/>
</OutsideClickHandler>
The problem:
For the <OutsideClickHandler> I pass a function as a prop that sets the states of my sidebar and hamburger to false so that whenever I click outside the component it can only hide it. I have another function that my hamburger onClick event executes as per below code:
sideBarToggler = () => {
this.setState((prevState) => {
return { sideBarOpen: !prevState.sideBarOpen, hamburgerOpen: !prevState.hamburgerOpen }})
}
My hamburger opens the <SideBar> fine, but when I try to click on the hamburger again I can't close the <SideBar> because my hamburger is outside the <OutsideClickHandler>. As sideBarToggler is called upon hamburger click it changes the state but onOutsideClick={this.hideSideBar} is called as well and states get changed back instantly. How can I fix the abomination I have written?
EDIT
hideSideBar function:
hideSideBar = () => {
this.setState({sideBarOpen: false, hamburgerOpen: false })
}

Focus on div without click in React to enable keyboard navigation on a module

I am coding an image gallery from scratch in React, when clicking on an image, a modal pops up (separate component from my gallery component). I want to navigate between the pictures with left and right arrow, not just with the added arrows on the screen (onclick) but at the moment it only focuses on the modal when I click on it once, then I can navigate with the keyboard too (onKeyDown).
I have added tabIndex="0" to my div, but I still need to click on the div once to focus on it.
<div tabIndex="0" onKeyDown={(event) => this.onKeyNavigation(event, ImageUrl, currentIndex, ImageUrls)}>
onKeyNavigation = (event, ImageUrl, currentIndex, ImageUrls) => {
if ((event.keyCode) === 39) {
this.props.loadNext(ImageUrl, currentIndex, ImageUrls)
}
else if ((event.keyCode) === 37) {
this.props.loadPrevious(ImageUrl, currentIndex, ImageUrls)
}
else if ((event.keyCode) === 27) {
this.props.onClose()
}
}
You'll need to fire a focus() event on the <div> you want to have focus after it has rendered.
The easiest way to do this is to use React's built-in lifecycle methods. First, create a ref for the element you want to have focus (in this case, the div listening for keyDown events). Then, you can call focus() on that node in your component's componentDidMount() method:
class ImageGallery extends React.Component {
construtor(){
super();
// Create the ref in the constructor
this.focusRef = React.createRef();
}
/* your other methods here */
componentDidMount(){
// Focus on the rendered div using the DOM focus() method
this.focusRef.focus();
}
render(){
// Set the ref in your render() method
return(<div ref={this.focusRef} onKeyDown={this.handle}></div>);
}
}
So the solution was:
componentDidUpdate(){
this.focusRef.current.focus();
}
dcastrodale your answer helped me a lot, but for some reason this would not work until I placed the ref in the componentDidMount() inside of a setTimeout(). This is what I did to focus on a specific div when the page loaded.
//This line was added in the constructor().
this.drumMachineAndOuterControllerRef = React.createRef();
//This is how my componentDidMount() looks like.
componentDidMount() {
setTimeout(() => {
this.drumMachineAndOuterControllerRef.current.focus();
}, 5);
}
//This is the div that is put in focus after the page loads.
<div id="drum-machine-and-outer-controller" tabIndex="0" onKeyDown={this.keyboardPress} ref={this.drumMachineAndOuterControllerRef}>
This works great for me and I could even press the button elements with the keyboard without the mouse pointer ever entering the browser window.
Thanks again dcastrodale! Your answer guided me in the right direction.

Resources