Reputation: 791
I am trying to creating an arrow-up button, which will be set to display: "none"
if I am at a top of my page, and when I scroll down further, I want to set it to display: "block". How can i achieve this? I am trying to manipulate the DOM inside my handleScroll function but it doesn't work.
My TopButton.js component
import React from "react";
import "../assets/arrow-up.png";
class TopButton extends React.Component {
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount = () => {
window.addEventListener("scroll", this.handleScroll, true);
};
handleClick = () => {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
};
handleScroll = () => {
console.log("scroll");
let x = document.getElementsByClassName("topbutton_button");
var x = document.getElementsByClassName("topbutton_button");
// x.style.display = "none";
// console.log(event.target.className);
// if (
// document.body.scrollTop > 40 ||
// document.documentElement.scrollTop > 40
// ) {
// style.display = "block";
// } else {
// display = "none";
// }
};
render() {
return (
<div className="topbutton_container">
<button
style={{ display: "block" }}
onClick={this.handleClick}
onScroll={this.handleScroll}
className="topbutton_button"
>
<img src={require("../assets/arrow-up.png")} />
</button>
</div>
);
}
}
export default TopButton;
Upvotes: 3
Views: 221
Reputation: 1074138
There are at least two reasons it didn't work:
See this question's answers; basically, getElementsByClassName
returns an HTMLCollection
, not a single element, but your commented-out code was treating it as though it were a single element.
If your component was ever re-rendered, it would be rendered in its default state, not the updated state you changed via the DOM
But that's not how you'd do it with React. Instead, you'd:
have the button state (whether it should be block or not) held as state in your component;
use that state when rendering the topbutton_button
, setting its style or class accordingly; and
update that state in your handleScroll
handler
A couple of others notes:
You also need to remove your handler when the component is unmounting
You shouldn't use arrow functions for component lifecycle functions
You don't need to use bind
on an arrow function (handleScroll
for instance). Either make it an arrow function or use bind
in the constructor to bind it.
Something along these lines, see the ***
comments
import React from "react";
import "../assets/arrow-up.png";
// *** Reusable function to decide whether we're "at the top" or not
function bodyIsAtTop() {
return (
document.body.scrollTop <= 40 &&
document.documentElement.scrollTop <= 40
);
}
class TopButton extends React.Component {
constructor(props) {
super(props);
// *** Initial state
this.state = {
atTop: bodyIsAtTop()
};
// *** No need for the following if you use an arrow function
// this.handleScroll = this.handleScroll.bind(this);
}
// *** Don't make this an arrow, make it a method
componentDidMount() {
window.addEventListener("scroll", this.handleScroll, true);
};
// *** Need to unbind when unmounted
componentWillUnmount = () => {
window.removeEventListener("scroll", this.handleScroll, true);
};
handleClick = () => {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
};
handleScroll = () => {
// *** Update state (possibly; if the flag isn't different, this doesn't do anything)
this.setState({atTop: bodyIsAtTop()});
};
render() {
// *** Get the flag from state, use it below in style
const {atTop} = this.state;
return (
<div className="topbutton_container">
<button
style={{ display: atTop ? "none" : "block" }}
onClick={this.handleClick}
onScroll={this.handleScroll}
className="topbutton_button"
>
<img src={require("../assets/arrow-up.png")} />
</button>
</div>
);
}
}
export default TopButton;
There I've kept your arrow functions for handleScroll
and handleClick
. There's an argument for making them methods and using bind
in the constructor instead, but it's mostly a style thing. (Well...style and it's easier to mock prototype methods for testing, which is a non-style reason for using prototype methods and bind
.)
Upvotes: 3