How to scroll a React page when it loads?
Probably easier to do this with the react-scrollable-anchor
library:
import React from "react";
import ReactDOM from "react-dom";
import ScrollableAnchor from "react-scrollable-anchor";
class App extends React.Component {
render() {
return (
<React.Fragment>
<div style={{ marginBottom: 7000 }}>nothing to see here</div>
<div style={{ marginBottom: 7000 }}>never mind me</div>
<ScrollableAnchor id={"doge"}>
<div>bork</div>
</ScrollableAnchor>
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can see it working by going here: https://zwoj0xw503.codesandbox.io/#doge
You can simplify your code by using react-router-dom
's HashRouter with React's ref.current.scrollIntoView()
or window.scrollTo(ref.currrent.offsetTop)
.
Currently tested and working on Chrome (desktop/android), Firefox(desktop), Silk Browser(android), Saumsung Internet(android), and Safari(iOS -- with one caveat in that it instantly jumps to the selection). While I was able to test and confirm it's working within the sandbox environment's iframe
, you'll have to adapt/test it within a standalone iframe
.
I'd also like to point out that your approach is very jQuery
like and that you should avoid directly attempting to utilize or manipulate the DOM
and/or the window
. In addition, you seem to use a lot of let
variables throughout your code, but they remain unchanged. Instead, they should be a const
variable as React evaluates variables each time it re-renders, so unless you plan on changing that variable during the re-render process, there's no need to use let
.
Working example: https://v6y5211n4l.codesandbox.io
components/ScrollBar
import React, { Component } from "react";
import PropTypes from "prop-types";
import Helmet from "react-helmet";
import Select from "react-select";
import { withRouter } from "react-router-dom";
import "./styles.css";
const scrollOpts = {
behavior: "smooth",
block: "start"
};
class ScrollBar extends Component {
constructor(props) {
super(props);
const titleRefs = props.searchOptions.map(
({ label }) => (this[label] = React.createRef())
); // map over options and use "label" as a ref name; ex: this.orange
this.topWindow = React.createRef();
this.searchRef = React.createRef();
const pathname = props.history.location.pathname.replace(/\//g, ""); // set current pathname without the "/"
this.state = {
titleRefs,
menuIsOpen: false,
isValid: props.searchOptions.some(({ label }) => label === pathname), // check if the current pathname matches any of the labels
pathname
};
}
componentDidMount() {
const { pathname, isValid } = this.state;
if (isValid) this.scrollToElement(); // if theres a pathname and it matches a label, scroll to it
if (!isValid && pathname) this.searchRef.current.select.focus(); // if there's a pathname but it's invalid, focus on the search bar
}
// allows us to update state based upon prop updates (in this case
// we're looking for changes in the history.location.pathname)
static getDerivedStateFromProps(props, state) {
const nextPath = props.history.location.pathname.replace(/\//g, "");
const isValid = props.searchOptions.some(({ label }) => label === nextPath);
return state.pathname !== nextPath ? { pathname: nextPath, isValid } : null; // if the path has changed, update the pathname and whether or not it is valid
}
// allows us to compare new state to old state (in this case
// we're looking for changes in the pathname)
componentDidUpdate(prevProps, prevState) {
if (this.state.pathname !== prevState.pathname) {
this.scrollToElement();
}
}
scrollToElement = () => {
const { isValid, pathname } = this.state;
const elem = isValid ? pathname : "topWindow";
setTimeout(() => {
window.scrollTo({
behavior: "smooth",
top: this[elem].current.offsetTop - 45
});
}, 100);
};
onInputChange = (options, { action }) => {
if (action === "menu-close") {
this.setState({ menuIsOpen: false }, () =>
this.searchRef.current.select.blur()
);
} else {
this.setState({ menuIsOpen: true });
}
};
onChange = ({ value }) => {
const { history } = this.props;
if (history.location.pathname.replace(/\//g, "") !== value) { // if the select value is different than the previous selected value, update the url -- required, otherwise, react-router will complain about pushing the same pathname/value that is current in the url
history.push(value);
}
this.searchRef.current.select.blur();
};
onFocus = () => this.setState({ menuIsOpen: true });
render() {
const { pathname, menuIsOpen, titleRefs } = this.state;
const { searchOptions, styles } = this.props;
return (
<div className="container">
<Helmet title={this.state.pathname || "Select an option"} />
<div className="heading">
<Select
placeholder="Search..."
isClearable={true}
isSearchable={true}
options={searchOptions}
defaultInputValue={pathname}
onFocus={this.onFocus}
onInputChange={this.onInputChange}
onChange={this.onChange}
menuIsOpen={menuIsOpen}
ref={this.searchRef}
value={null}
styles={styles || {}}
/>
</div>
<div className="fruits-list">
<div ref={this.topWindow} />
{searchOptions.map(({ label, value }, key) => (
<div key={value} id={value}>
<p ref={titleRefs[key]}>{label}</p>
{[1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14].map(num => (
<p key={num}>{num}</p>
))}
</div>
))}
</div>
</div>
);
}
}
ScrollBar.propTypes = {
searchOptions: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
}).isRequired
).isRequired,
styles: PropTypes.objectOf(PropTypes.func)
};
export default withRouter(ScrollBar);
index.js
import React from "react";
import { render } from "react-dom";
import { HashRouter } from "react-router-dom";
import Helmet from "react-helmet";
import ScrollBar from "../src/components/ScrollBar";
const config = {
htmlAttributes: { lang: "en" },
title: "My App",
titleTemplate: "Scroll Search - %s",
meta: [
{
name: "description",
content: "The best app in the world."
}
]
};
const searchOptions = [
{
label: "apple",
value: "apple"
},
{
label: "orange",
value: "orange"
},
{
label: "banana",
value: "banana"
},
{
label: "strawberry",
value: "strawberry"
}
];
const styles = {
menu: base => ({ ...base, width: "100%", height: "75vh" }),
menuList: base => ({ ...base, minHeight: "75vh" }),
option: base => ({ ...base, color: "blue" })
};
const App = () => (
<HashRouter>
<Helmet {...config} />
<ScrollBar searchOptions={searchOptions} styles={styles} />
</HashRouter>
);
render(<App />, document.getElementById("root"));