In the Clarity Components documentation there is a working example for "Dimissing labels", but unfortunately there is no example code for this case.
See https://v2.clarity.design/labels
The documentation says - A label can be dismissed. Use a close icon at the right-most side of a label to dismiss it.
How do I create the label in the example?
I tried the following without success (just hoping for the best)
<span class="label" >james#test.com<clr-icon shape="close" ></clr-icon></span>
There is not an implementation of this behavior, rather it is just a CSS/HTML example that shows what the pattern would look like. It is up to the application to handle the 'dismissing' of the label by removing the label on click of the close button.
You can write a directive, say dismissable and have it add a X icon to labels, and which could emit an event whenever user clicks the X.
import { Directive, ElementRef, Renderer2, AfterViewInit, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[dismissable]'
})
export class DismissabelDirective implements AfterViewInit {
#Output()
close = new EventEmitter();
constructor(private renderer: Renderer2, private elRef: ElementRef) {
}
ngAfterViewInit() {
let icon = this.renderer.createElement('clr-icon');
icon.setAttribute('shape', 'close');
icon.style.margin = '1rem;'
this.renderer.setStyle(icon, 'margin-left', '0.5rem');
this.renderer.setStyle(icon, 'margin-right', '-0.25rem');
this.renderer.setStyle(icon, 'cursor', 'pointer');
this.renderer.appendChild(this.elRef.nativeElement, icon);
this.renderer.listen(icon, 'click', () => {this.close.emit(); return true;})
}
}
You could use it like below:
app.component.html
<div class="container">
<span class="label" dismissable (close)="delete(item)" *ngFor="let item of items">{{item}}</span>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
items = ['Apple', 'Orange', 'Banana'];
delete(item) {
const index: number = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
}
}
Here is complete StackBlitz: https://stackblitz.com/edit/clarity-light-theme-v2-dismissable-labels
I have a program that includes some nested custom elements. The leaves of one of these component's shadowRoot contains instances of an element like <div contentEditable>. In Chrome79 and Chromium-Edge-Beta the contentEditable feature works as one would expect it to - that is, the elements focus when you click or tab to them, show a focus outline, and are editable. In FireFox72 they behave erratically, mainly in that clicking on one will focus on it only some of the time, and that while they can be tabbed to, they do not focus such that they can be typed into.
After some whittling, I think I've arrived at a minimal reproduction. It is two custom elements: A root element ce-main and the leaf element ce-leaf that is instantiated arbitrarily many times from within ce-main and attached to ce-main's shadowRoot.
class Main extends HTMLElement {
constructor() { super(); }
connectedCallback() {
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<style>
[contentEditable] {
min-height:2em;
padding:.5em;
border:1px dashed rgba(0,0,0,.0625);
}
[contentEditable]:empty::before {
color: rgba(0,0,0,.15);
content: "You should be able to focus and type here.";
cursor:text;
}
</style>
<div id="container" style=""></div>`;
customElements.whenDefined("ce-leaf").then(
() => this.constructFromSomeDataSource()
);
}
constructFromSomeDataSource() {
let rows = [];
for (let i = 0; i < 4; i++) {
let leaf = document.createElement("ce-leaf");
this.shadowRoot.querySelector("#container").appendChild(leaf);
};
}
}
class Leaf extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `
<div contentEditable></div>
`;
}
}
customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
<ce-main></ce-main>
If we do without the shadowRoot, everything is nicely focusable in Chrome/EdgeBeta/Firefox:
class Main extends HTMLElement {
constructor() { super(); }
connectedCallback() {
customElements.whenDefined("ce-leaf").then(
() => this.constructFromSomeDataSource()
);
}
constructFromSomeDataSource() {
let rows = [];
for (let i = 0; i < 4; i++) {
let leaf = document.createElement("ce-leaf");
this.appendChild(leaf);
};
}
}
class Leaf extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `
<div contentEditable></div>
`;
}
}
customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
[contentEditable] {
min-height:2em;
padding:.5em;
border:1px dashed rgba(0,0,0,.0625);
}
[contentEditable]:empty::before {
color: rgba(0,0,0,.15);
content: "You should be able to focus and type here.";
cursor:text;
}
<ce-main></ce-main>
Can anyone verify if this is a bug in FF, or if I am simply doing something that is not in line with how it should be done in FF?
Had to dig through many Firefox/Focus posts.
Similar behaviour in a FireFox bug going back some 6 years: https://bugzilla.mozilla.org/show_bug.cgi?id=904846
Workaround
Found the best approach here: Clicking outside a contenteditable div stills give focus to it?
Handle the contenteditable attribute and setting focus() yourself with click & blur events:
(note: leafCounter is valid CSS, just does not work in StackOverflow inline code, works in JSFiddle)
class Main extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.attachShadow({ mode: "open" })
.innerHTML = `<style>
ce-leaf div {
padding: .5em;
cursor: text;
counter-increment: leafCounter;
}
ce-leaf div:empty::before {
color: lightgrey;
content: "placeholder text #" counter(leafCounter);
}
[contenteditable]:focus{
background: lightgreen;
}
</style>` + "<ce-leaf></ce-leaf>".repeat(5);
}
}
class Leaf extends HTMLElement {
constructor() {
super();
let div = this.appendChild(document.createElement("div"));
div.addEventListener("click", evt => {
evt.target.contentEditable = true;
evt.target.focus();
});
div.addEventListener("blur", evt => {
evt.target.contentEditable = false;
});
}
}
customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
<ce-main></ce-main>
<ce-leaf> IS an element!
You don't need that DIV inside a <ce-leaf> Custom Element...
JSFiddle version does the contenteditable on <ce-leaf>
https://jsfiddle.net/dannye/udmgL03p/
constructor() {
super();
this.addEventListener("click", evt => {
this.contentEditable = true;
this.focus();
});
this.addEventListener("blur", evt => {
this.contentEditable = false;
});
}
Update: Alas another Firefox with contenteditable bug: You can't select part of a text and replace it in the JSfiddle..
stick with the DIV inside an element solution.
Is there anyway to know how many children a named slot contains? In my Stencil component I have something like this in my render function:
<div class="content">
<slot name="content"></slot>
</div>
What I want to do is style the div.content differently depending on how many children are inside the slot. If there are no children in the slot, then div.content's style.display='none', otherwise, I have a bunch of styles applied to div.content that make the children appear correctly on the screen.
I tried doing:
const divEl = root.querySelector( 'div.content' );
if( divEl instanceof HTMLElement ) {
const slotEl = divEl.firstElementChild;
const hasChildren = slotEl && slotEl.childElementCount > 0;
if( !hasChildren ) {
divEl.style.display = 'none';
}
}
however this is always reporting hasChildren = false even when I have items inserted into the slot.
If you are querying the host element you will get all the slotted content inside of it. That means that the host element's children are going to be all the content that will be injected into the slot.
For example try to use the following code to see it in action:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true
})
export class MyComponent {
#Element() host: HTMLElement;
#State() childrenData: any = {};
componentDidLoad() {
let slotted = this.host.children;
this.childrenData = { hasChildren: slotted && slotted.length > 0, numberOfChildren: slotted && slotted.length };
}
render() {
return (
<div class="content">
<slot name="content"></slot>
<div>
Slot has children: {this.childrenData.hasChildren ? 'true' : 'false'}
</div>
<div>
Number of children: {this.childrenData.numberOfChildren}
</div>
</div>);
}
}
The accepted solution is actually not the correct way to do it. Even the code example is wrong. It is using a named slot name="content". Only elements with slot="content" attribute from the light DOM will be slotted into that slot; hence simply checking this.host.children is not sufficient at all.
Instead, you should work with the slotchange event (which also has the benefit of properly reflecting dynamic changes):
import {Component, Element, State} from '#stencil/core';
export type TSlotInfo = {
hasSlottedElements?: boolean;
numberOfSlottedElements?: number;
}
#Component({
tag: 'my-component',
shadow: true
})
export class MyComponent {
#Element() host: HTMLElement;
#State() slotInfo: TSlotInfo = {};
handleSlotChange = (event: Event) => {
let assignedElementCount = event.currentTarget.assignedElements().length;
this.slotInfo = {
hasSlottedElements: Boolean(assignedElementCount),
numberOfSlottedElements: assignedElementCount,
}
}
render() {
return (
<div class="content">
<slot name="content" onslotchange={this.handleSlotChange}></slot>
<div>
Slot is populated: {this.slotInfo.hasSlottedElements}
</div>
<div>
Number of slotted elements: {this.slotInfo.numberOfSlottedElements}
</div>
</div>);
}
}
I'm trying to test my component injected into a mock class I created. Although the component works when I try to test its existence it returns null.
Injectable Component:
import { Injectable, ElementRef, Renderer2, RendererFactory2 } from '#angular/core';
#Injectable()
export class NgBackdropComponent {
private renderer: Renderer2;
private appElementRef: ElementRef;
message: string = 'Carregando...';
constructor(rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
}
show() {
const divSpinnerItem1 = this.renderer.createElement('i');
const divSpinnerItem2 = this.renderer.createElement('i');
const divSpinnerItem3 = this.renderer.createElement('i');
const divSpinner = this.renderer.createElement('div');
this.renderer.addClass(divSpinner, 'spinner');
this.renderer.appendChild(divSpinner, divSpinnerItem1);
this.renderer.appendChild(divSpinner, divSpinnerItem2);
this.renderer.appendChild(divSpinner, divSpinnerItem3);
const spanMensagem = this.renderer.createElement('span');
spanMensagem.innerHTML = this.message;
const div = this.renderer.createElement('div');
this.renderer.addClass(div, 'lock-content');
this.renderer.appendChild(div, divSpinner);
this.renderer.appendChild(div, spanMensagem);
this.renderer.appendChild(this.appElementRef.nativeElement, div);
}
hide() {
const elemento = this.appElementRef.nativeElement.querySelector('.lock-content');
if (elemento) {
elemento.remove();
}
}
}
my testing environment:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { NgBackdropComponent } from './ng-backdrop.component';
import { Component } from '#angular/core';
import { By } from '#angular/platform-browser';
#Component({
template: `
<button (click)="clickButton()"></button>
`
})
class MockNgBackdropComponent {
constructor(private backdrop: NgBackdropComponent) { }
clickButton() {
this.backdrop.message = 'Teste BackDrop aesdas';
this.backdrop.show();
console.log('iniciei backdrop');
}
closeBackdrop() {
this.backdrop.hide();
}
}
describe('NgBackdropComponent', () => {
let component: MockNgBackdropComponent;
let fixture: ComponentFixture<MockNgBackdropComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MockNgBackdropComponent],
providers: [NgBackdropComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MockNgBackdropComponent);
component = fixture.componentInstance;
});
describe('Deve injetar', async () => {
it('Deve ter uma div principal', function () {
const btnClick = fixture.nativeElement.querySelector('button');
btnClick.click();
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('.lock-content');
console.log(el);
expect(el).toBeTruthy();
});
});
});
In testing I create a Mock class where I inject my component.
I do not understand why it can not find the class because it exists.
The reason you can't find it in the component is because you did not create it in the component. If you look at this line in your constructor:
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0))
You are creating it on the document directly in the <body> element. If you search for that in your spec, you will find it there. I created a STACKBLITZ to show you what I mean. Here is the spec from that stackblitz:
it('Deve ter uma div principal', () => {
const btnClick = fixture.nativeElement.querySelector('button');
console.log(btnClick);
btnClick.click();
fixture.detectChanges();
const appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
const el = appElementRef.nativeElement.querySelector('.lock-content');
expect(el).toBeTruthy();
});
Adding a little more clarification:
If you console.log(appElementRef) you'll notice that its tagName is body, and note the contents of its nativeElement.innerHTML Here is what that would look like "prettyfied":
<body>
<div class="jasmine_html-reporter">
<div class="jasmine-banner"><a class="jasmine-title" href="http://jasmine.github.io/" target="_blank"></a><span
class="jasmine-version">3.3.0</span></div>
<ul class="jasmine-symbol-summary"></ul>
<div class="jasmine-alert"></div>
<div class="jasmine-results">
<div class="jasmine-failures"></div>
</div>
</div>
<div id="nprogress" style="transition: none 0s ease 0s; opacity: 1;">
<div class="bar" role="bar" style="transform: translate3d(0%, 0px, 0px); transition: all 200ms ease 0s;">
<div class="peg"></div>
</div>
</div>
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
<div class="lock-content">
<div class="spinner">
<i></i>
<i></i>
<i></i>
</div>
<span>Teste BackDrop aesdas</span>
</div>
</body>
Note how the button was created within the div with id="root0"? However, the div with class="lock-content" was created right off the root <body> element, and therefore is not within the div of the component.
In fact, you can see this very clearly when you console.log(fixture.nativeElement) and see that the tagName is "div", its innerHTML is <button></button>, and it has two attributes: id: "root0" and ng-version: "7.0.1". Put that all together and it looks like this:
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
So you can clearly see that you cannot find the div you created in the component because you created it outside the component.
I hope this helps.
I think you should use DebugElement, for example:
it('Deve ter uma div principal', function () {
const btnClick = fixture.debugElement.query(By.css('button'));
btnClick.click();
fixture.detectChanges();
const el = fixture.debugElement.query(By.css('.lock-content'));
console.log(el);
expect(el).toBeTruthy();
});
Follow this link for more information.
I have a problem, which I have no ideas, how to solve.
In my react component I display a long list of data and few links at the bottom.
After clicking on any of this links I fill in the list with new collection of the links and need to scroll to the top.
The problem is - how to scroll to the top after new collection is rendered?
'use strict';
// url of this component is #/:checklistId/:sectionId
var React = require('react'),
Router = require('react-router'),
sectionStore = require('./../stores/checklist-section-store');
function updateStateFromProps() {
var self = this;
sectionStore.getChecklistSectionContent({
checklistId: this.getParams().checklistId,
sectionId: this.getParams().sectionId
}).then(function (section) {
self.setState({
section,
componentReady: true
});
});
this.setState({componentReady: false});
}
var Checklist = React.createClass({
mixins: [Router.State],
componentWillMount: function () {
updateStateFromProps.call(this);
},
componentWillReceiveProps(){
updateStateFromProps.call(this);
},
render: function () {
if (this.state.componentReady) {
return(
<section className='checklist-section'>
<header className='section-header'>{ this.state.section.name } </header>
<Steps steps={ this.state.section.steps }/>
<a href=`#/${this.getParams().checklistId}/${this.state.section.nextSection.Id}`>
Next Section
</a>
</section>
);
} else {...}
}
});
module.exports = Checklist;
Finally.. I used:
componentDidMount() {
window.scrollTo(0, 0)
}
EDIT: React v16.8+
useEffect(() => {
window.scrollTo(0, 0)
}, [])
Since the original solution was provided for very early version of react, here is an update:
constructor(props) {
super(props)
this.myRef = React.createRef() // Create a ref object
}
componentDidMount() {
this.myRef.current.scrollTo(0, 0);
}
render() {
return <div ref={this.myRef}></div>
} // attach the ref property to a dom element
You could use something like this. ReactDom is for react.14. Just React otherwise.
componentDidUpdate = () => { ReactDom.findDOMNode(this).scrollIntoView(); }
Update 5/11/2019 for React 16+
constructor(props) {
super(props)
this.childDiv = React.createRef()
}
componentDidMount = () => this.handleScroll()
componentDidUpdate = () => this.handleScroll()
handleScroll = () => {
const { index, selected } = this.props
if (index === selected) {
setTimeout(() => {
this.childDiv.current.scrollIntoView({ behavior: 'smooth' })
}, 500)
}
}
In React Routing there is the problem that if we redirect to the new route, then it won't automatically take you to the top of the page.
Even I did have the same issue.
I just added the single line to my component and it worked like butter.
componentDidMount() {
window.scrollTo(0, 0);
}
Refer: react training
Hook solution:
Create a ScrollToTop hook
import { useEffect } from "react";
import { withRouter } from "react-router-dom";
const ScrollToTop = ({ children, location: { pathname } }) => {
useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth"
});
}, [pathname]);
return children || null;
};
export default withRouter(ScrollToTop);
Wrap your App with it
<Router>
<ScrollToTop>
<App />
</ScrollToTop>
</Router>
Documentation : https://reacttraining.com/react-router/web/guides/scroll-restoration
For those using hooks, the following code will work.
React.useEffect(() => {
window.scrollTo(0, 0);
}, []);
Note, you can also import useEffect directly: import { useEffect } from 'react'
This could, and probably should, be handled using refs:
"... you can use ReactDOM.findDOMNode as an "escape hatch" but we don't recommend it since it breaks encapsulation and in almost every case there's a clearer way to structure your code within the React model."
Example code:
class MyComponent extends React.Component {
componentDidMount() {
this._div.scrollTop = 0
}
render() {
return <div ref={(ref) => this._div = ref} />
}
}
You can do this in the router like that:
ReactDOM.render((
<Router onUpdate={() => window.scrollTo(0, 0)} history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home}></IndexRoute>
<Route path="/about" component={About}/>
<Route path="/work">
<IndexRoute component={Work}></IndexRoute>
<Route path=":id" component={ProjectFull}></Route>
</Route>
<Route path="/blog" component={Blog}/>
</Route>
</Router>
), document.getElementById('root'));
The onUpdate={() => window.scrollTo(0, 0)} put the scroll top.
For more information check: codepen link
This works for me.
import React, { useEffect } from 'react';
useEffect(() => {
const body = document.querySelector('#root');
body.scrollIntoView({
behavior: 'smooth'
}, 500)
}, []);
Here's yet another approach that allows you to choose which mounted components you want the window scroll position to reset to without mass duplicating the ComponentDidUpdate/ComponentDidMount.
The example below is wrapping the Blog component with ScrollIntoView(), so that if the route changes when the Blog component is mounted, then the HOC's ComponentDidUpdate will update the window scroll position.
You can just as easily wrap it over the entire app, so that on any route change, it'll trigger a window reset.
ScrollIntoView.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
export default WrappedComponent => {
class ResetWindowScroll extends Component {
componentDidUpdate = (prevProps) => {
if(this.props.location !== prevProps.location) window.scrollTo(0,0);
}
render = () => <WrappedComponent {...this.props} />
}
return withRouter(ResetWindowScroll);
}
Routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from '../components/App';
import About from '../components/pages/About';
import Blog from '../components/pages/Blog'
import Index from '../components/Landing';
import NotFound from '../components/navigation/NotFound';
import ScrollIntoView from '../components/navigation/ScrollIntoView';
export default (
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="/about" component={About} />
<Route path="/blog" component={ScrollIntoView(Blog)} />
<Route path="*" component={NotFound} />
</Route>
);
The above example works great, but if you've migrated to react-router-dom, then you can simplify the above by creating a HOC that wraps the component.
Once again, you could also just as easily wrap it over your routes (just change componentDidMount method to the componentDidUpdate method example code written above, as well as wrapping ScrollIntoView with withRouter).
containers/ScrollIntoView.js
import { PureComponent, Fragment } from "react";
class ScrollIntoView extends PureComponent {
componentDidMount = () => window.scrollTo(0, 0);
render = () => this.props.children
}
export default ScrollIntoView;
components/Home.js
import React from "react";
import ScrollIntoView from "../containers/ScrollIntoView";
export default () => (
<ScrollIntoView>
<div className="container">
<p>
Sample Text
</p>
</div>
</ScrollIntoView>
);
This solution is working for the Functional component as well as the Class Base.
First of all, I do not like the idea of Scroll to top on every re-render, instead, I like of attache function to the particular event.
Step #1: Create a function to ScrollToTop
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
Step #2: Call this function on an event e.g onClick
onRowClick={scrollToTop()}
// onClick={scrollToTop()}
// etc...
I'm using react-router ScrollToTop Component which code described in react-router docs
https://reacttraining.com/react-router/web/guides/scroll-restoration/scroll-to-top
I'm changing code in single Routes file and after that no need of change code in every component.
Example Code -
Step 1 - create ScrollToTop.js Component
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class ScrollToTop extends Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}
render() {
return this.props.children
}
}
export default withRouter(ScrollToTop)
Step 2 - In App.js file, add ScrollToTop Component after <Router
const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)
If all want to do is something simple here is a solution that will work for everybody
add this mini function
scrollTop()
{
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
call the function as following from the footer of the page
<a className="scroll-to-top rounded" style={{display: "inline"}} onClick={this.scrollTop}>TOP</a>
if you want to add nice styles here is the css
.scroll-to-top {
position: fixed;
right: 1rem;
bottom: 1rem;
display: none;
width: 2.75rem;
height: 2.75rem;
text-align: center;
color: #fff;
background: rgba(90, 92, 105, 0.5);
line-height: 46px;
}
This is the only thing that worked for me (with an ES6 class component):
componentDidMount() {
ReactDOM.findDOMNode(this).scrollIntoView();
}
All of the above didn't work for me - not sure why but:
componentDidMount(){
document.getElementById('HEADER').scrollIntoView();
}
worked, where HEADER is the id of my header element
I have tried #sledgeweight solution but it does not work well for some of the views. But adding a setTimeout seems to work perfectly. In case someone facing the same issue as me. Below is my code.
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
const ScrollToTop = () => {
const { pathname } = useLocation()
useEffect(() => {
console.log(pathname)
/* settimeout make sure this run after components have rendered. This will help fixing bug for some views where scroll to top not working perfectly */
setTimeout(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, 0)
}, [pathname])
return null
}
export default ScrollToTop
Use it in AppRouter.js as
<Router>
<ScrollToTop/>
<App>
</Router>
Using Hooks in functional components,
assuming the component updates when theres an update in the result props
import React, { useEffect } from 'react';
export const scrollTop = ({result}) => {
useEffect(() => {
window.scrollTo(0, 0);
}, [result])
}
The page that appears after clicking, just write into it.
componentDidMount() {
window.scrollTo(0, 0);
}
Smooth scroll to top . In hooks you can use this method inside lifecycle mounting state for once render
useEffect(() => {
window.scrollTo({top: 0, left: 0, behavior: 'smooth' });
}, [])
Looks like all the useEffect examples dont factor in you might want to trigger this with a state change.
const [aStateVariable, setAStateVariable] = useState(false);
const handleClick = () => {
setAStateVariable(true);
}
useEffect(() => {
if(aStateVariable === true) {
window.scrollTo(0, 0)
}
}, [aStateVariable])
I tried everything, but this is the only thing that worked.
useLayoutEffect(() => {
document.getElementById("someID").scrollTo(0, 0);
});
This is what I did:
useEffect(() => ref.current.scrollTo(0, 0));
const ref = useRef()
return(
<div ref={ref}>
...
</div>
)
I was doing a SPA in React 17.0 using functional components and window.scroll, window.scrollTo and all of this variants doesn't work for me. So I made a solution using useRef hook. I created a span tag in the top of the component with Ref and then I used and effect with ref.current.scrollIntoView()
There is a short example:
import React, { useEffect,useRef} from 'react';
export const ExampleComponent = () => {
const ref = useRef();
useEffect(() => {
ref.current.scrollIntoView()
}, []);
return(
<>
<span ref={ref}></span>
<YourCodeHere />
<MoreCode />
</>
)
}
For React v18+ my recommendation will be to use wrapper component, will be the easiest way to execute.
Step 1: Create a ScrollToTop component (component/ScrollToTop.js)
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
Step 2: Wrap your App with it (index.js)
<React.StrictMode>
<BrowserRouter>
<ScrollToTop />
<App />
</BrowserRouter>
</React.StrictMode>
Explanation: Every time pathname changes useEffect will be called to scroll the page to the top.
I'm using React Hooks and wanted something re-usable but also something I could call at any time (rather than just after render).
// utils.js
export const useScrollToTop = (initialScrollState = false) => {
const [scrollToTop, setScrollToTop] = useState(initialScrollState);
useEffect(() => {
if (scrollToTop) {
setScrollToTop(false);
try {
window.scroll({
top: 0,
left: 0,
behavior: 'smooth',
});
} catch (error) {
window.scrollTo(0, 0);
}
}
}, [scrollToTop, setScrollToTop]);
return setScrollToTop;
};
Then to use the hook you can do:
import { useScrollToTop } from 'utils';
const MyPage = (props) => {
// initialise useScrollToTop with true in order to scroll on page load
const setScrollToTop = useScrollToTop(true);
...
return <div onClick={() => setScrollToTop(true)}>click me to scroll to top</div>
}
I ran into this issue building a site with Gatsby whose Link is built on top of Reach Router. It seems odd that this is a modification that has to be made rather than the default behaviour.
Anyway, I tried many of the solutions above and the only one that actually worked for me was:
document.getElementById("WhateverIdYouWantToScrollTo").scrollIntoView()
I put this in a useEffect but you could just as easily put it in componentDidMount or trigger it any other way you wanted to.
Not sure why window.scrollTo(0, 0) wouldn't work for me (and others).
I had the same for problem for a while. Adding window.scrollTo(0, 0);to every page is painful and redundant. So i added a HOC which will wrap all my routes and it will stay inside BrowserRouter component:
<ScrollTop>
<Routes />
</ScrollTop>
Inside ScrollTopComponent we have the following:
import React, { useEffect } from "react";
import { useLocation } from "react-router-dom";
const ScrollTop = (props) => {
const { children } = props;
const location = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [location]);
return <main>{children}</main>;
};
export default ScrollTop;
Solution for functional component - Using useEffect() hook
useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);
If you are doing this for mobile, at least with chrome, you will see a white bar at the bottom.
This happens when the URL bar disappears. Solution:
Change the css for height/min-height: 100% to height/min-height: 100vh.
Google Developer Docs
None of the above answers is currently working for me. It turns out that .scrollTo is not as widely compatible as .scrollIntoView.
In our App.js, in componentWillMount() we added
this.props.history.listen((location, action) => {
setTimeout(() => { document.getElementById('root').scrollIntoView({ behavior: "smooth" }) }, 777)
})
This is the only solution that is working universally for us. root is the ID of our App. The "smooth" behavior doesn't work on every browser / device. The 777 timeout is a bit conservative, but we load a lot of data on every page, so through testing this was necessary. A shorter 237 might work for most applications.