I have built a React component which is suppose to call the function on window scroll event.
I would like to call the function, "getCards('default'), when the user scroll to 90% of the scroll height.
The component looks as shown in below:
class Home extends React.Component {
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
// test
this.props.getCards('default');
window.addEventListener('scroll', this.handleScroll);
}
handleScroll(event) {
console.log('scroll event');
}
Could anyone help me achieve this?
Thank you.
You have to work with your component, please refer to this code:
class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// test
this.props.getCards('default');
}
render() {
return (<div onScroll={this.handleScroll}>...</div>)
}
handleScroll(event) {
var heightBound = window.height * 0.8
if (heightBound > window.scrollY) {
// Probably you want to load new cards?
this.props.getCards(...);
}
}
The scroll event must be placed inside your component and binded using onScroll evt. The handler will check for percentage of used screen size and will load others elements if the lower bound limit is reached.
I hope this can help :)
I've got a similar problem and nothing with onScroll work to me.
constructor(props) {
super(props);
window.addEventListener('scroll', this.handleScroll, true);
}
handleScroll = (event) => {
// Your code
}
https://gist.github.com/koistya/934a4e452b61017ad611
Related
I am trying to change toolbar background color as #fff color when body scrolled.Otherwise it will be transparent.
Here is sample toolbar component:
export default class ToolBar extends React.Component {
constructor(props){
super(props)
this.state = {
scrolled:false
}
}
render() {
const { children, className,scrolled, ...other } = this.props
return (
<nav style={{backgroundColor:this.state.scrolled?'#fff':'transparent'}}>
{children}
</nav>
)
}
}
How can we do this with react?
Just add an event listener to window object.
componentDidMount() {
window.addEventListener('scroll', this.checkScroll);
}
checkScroll = () => {
this.setState({ scrolled: window.scrollY > 0 });
};
Note: You would probably also need some debounce to avoid rapid and multiple set states.
And remember to disconnect the listener on component destroy.
componentWillUnmount() {
window.removeEventListener('scroll', this.checkScroll);
}
When I leave a route component, I get the following warning:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an
unmounted component. This is a no-op.
The warning started appearing after I added the scroll event listener. How do I prevent this warning? I'm binding the event method only once. The event listener is added in componentDidMount and removed in componentWillUnmount.
class DashboardRoute extends React.Component {
constructor (props) {
super(props)
this.handleScroll = this.handleScroll.bind(this);
this.state = {
scrolled: false
};
}
componentDidMount () {
window.addEventListener('scroll', throttle(this.handleScroll, 250));
}
componentWillUnmount () {
window.removeEventListener('scroll', throttle(this.handleScroll, 250));
}
handleScroll (e) {
let scrolled = (window.scrollY > 0);
if (scrolled !== this.state.scrolled) {
this.setState({scrolled: scrolled});
}
}
render () {
return (
<div>
<!-- Component code -->
</div>
);
}
}
Since the scroll event handler can be delayed as much as 250 ms, your component might be unmounted before it is called. This depends on how DashboardRoute is used throughout your app.
You can use an instance variable called e.g. _mounted that keep track of if the component is actually still mounted or not and use that in your scroll handler before you use setState.
window.removeEventListener('scroll', throttle(this.handleScroll, 250)) will also try to remove a newly created function as scroll listener, so the listener created in window.addEventListener('scroll', throttle(this.handleScroll, 250)) will not be removed. You can create the throttled method once instead and reference that.
Example
class DashboardRoute extends React.Component {
_mounted = true;
state = {
scrolled: false
};
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
this._mounted = false;
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll = throttle(e => {
let scrolled = window.scrollY > 0;
if (this._mounted && scrolled !== this.state.scrolled) {
this.setState({ scrolled: scrolled });
}
}, 250);
render() {
return <div>{/* ... */}</div>;
}
}
Something that used to be simple is quite complicated when you don't know the React way.
I'm trying to create a component to act like a sticky header or footer when it reaches the top of the page.
At the moment I'm quite content in adding the below:
componentDidMount() {
window.addEventListener('scroll', this.onScroll, false);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.onScroll, false);
}
However I've hit a wall to how I get the scroll top position of a styled component. So lets say I had a styled component called Container and that outputted a form I usually just add a data attribute and do something like the below:
const container = document.getElementbyTag("data-sticky-container")
const position = container.offsetTop
How would I go about doing this in React?
Update
Now using ref. I've fallen into a problem where current isn't defined:
constructor(props) {
super(props);
this.optionBox = React.createRef();
}
componentDidMount() {
window.addEventListener('scroll', this.onScroll, false);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.onScroll, false);
}
onScroll() {
console.log(this.optionBox.current.offsetTop);
}
In react you would use a reference for your component: https://reactjs.org/docs/refs-and-the-dom.html
You would have something like this:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
getOffset = () => {
console.log(this.myRef.current.offsetTop);
}
render() {
return <Container ref={this.myRef} onClick={this.getOffset} />;
}
}
And now you can access the container offsetTop by this.myRef.current.offsetTop inside of your onScroll function like in getOffset
Another option is to use an innerRef. This can be better because it will give you a reference to the DOM node and not just a wrapper. See this answer.
You would then have more direct access to properties like scrollTop.
Unlike ref, innerRef uses a callback:
In your case it would look like:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef;
}
getOffset = () => {
console.log(this.myRef.offsetTop);
}
render() {
return <Container
innerRef={element => this.textInput = element}
onClick={this.getOffset} />;
}
}
You can add a ref
to the component for which u want an offsetTop. This ref will have all the computed css values of that component.
I'm trying to add infinite scroll in my web application.When the user scrolls down the page, there must be an API call to load the data beneath the existing data.So, the problem here is when I reach the bottom of the web page, the API is not being called.
import React from "react";
import { Link } from 'react-router-dom';
import axios from 'axios';
class InfiniteData extends React.Component{
constructor(props){
super(props);
this.state={olddata: [],newData: [], requestSent: false}
}
componentDidMount(){
window.addEventListener('scroll', this.handleOnScroll);
this.doQuery();
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleOnScroll);
}
doQuery() {
console.log("indoquery");
axios.get("https://jsonplaceholder.typicode.com/posts")
.then( res=>
this.setState({
olddata: res.data,
newData: this.state.olddata.concat(this.state.olddata)
})
)
.catch(console.log("error"))
}
handleOnScroll(){
var scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
var scrollHeight = (document.documentElement && document.documentElement.scrollHeight) || document.body.scrollHeight;
var clientHeight = document.documentElement.clientHeight || window.innerHeight;
var scrolledToBottom = Math.ceil(scrollTop + clientHeight) >= scrollHeight;
if (scrolledToBottom) {
console.log("At bottom");
// enumerate a slow query
setTimeout(this.doQuery, 2000);
}
}
render()
{
return (
<div>
<div className="data-container">
{this.state.newData && this.state.newData.map((dat,i)=>
<div key={i}>
{dat.body}
</div>
)}
</div>
</div>
);
}
}
export default InfiniteData;
This is actually just an obscured case of not binding this correctly: the following line calls handleOnScroll using window (not the InfiniteData component instance) as this:
window.addEventListener('scroll', this.handleOnScroll);
Then, your setTimeout call is trying to call this.doQuery, which is undefined since window.doQuery doesn't exist.
If you bind this correctly for the EventListener, this should work out: either change to window.addEventListener('scroll', this.handleOnScroll.bind(this)); in componentDidMount or add the following line inside the constructor to keep it bound across the board:
this.handleOnScroll = this.handleOnScroll.bind(this)
Note: this isn't the problem you're having, but be careful inside your setState call--do you mean to use newData: this.state.olddata.concat(res.data)? (passing res.data to the concat call)
Following should work.
import React from "react";
function doQuery() {
console.log("indoquery");
// Do something here
}
class InfiniteData extends React.Component{
constructor(props){
super(props);
this.state={olddata: [],newData: [], requestSent: false}
}
componentDidMount(){
window.addEventListener('scroll', this.handleOnScroll);
doQuery();
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleOnScroll);
}
handleOnScroll(){
// .....
if (scrolledToBottom) {
setTimeout(function () {doQuery()}, 2000);
}
}
render() {
return (
<div>
</div>
);
}
}
export default InfiniteData;
Note that I have removed some code to make it compact and easier for you to understand where the problem lies. Basically the fix is where you define the doQuery function and how you pass that function to setTimeout
As Daniel Thompson stated, the problem is that you do not bind this. To elaborate: when the eventListener invokes the function this will not be bound to your component, but rather to window.
Binding this in the event listener will solve the problem of the code not being called, but will create a new problem. When you call handleOnScroll.bind(this) a new function is created, so when you try to un-register it, it will not unregister. To get both registration and unregistration to work as expected, save that new function and use it to register/unregister.
constructor(props){
super(props);
// Need to bind 'this' to refer to component.
// This will create a new function which will have to be stored so it can be unregistered
this.scrollListener = this.handleOnScroll.bind(this);
this.state={olddata: [],newData: [], requestSent: false}
}
componentDidMount(){
window.addEventListener('scroll', this.scrollListener);
this.doQuery();
}
componentWillUnmount() {
window.removeEventListener('scroll', this.scrollListener);
}
I am rendering to the DOM with ReactDOM. I would like to know whether I have to manually unmount React components on window unload events. If so, what is the best practice here? What I can think of is the following:
class MyComponent extends React.Component {
constructure(props) {
super(props);
this.handleUnload = this.handleUnload.bind(this);
}
handleUnload() {
ReactDOM.unmountComponentAtNode(this.getDomNode());
}
componentWillMount() {
window.addEventListener('beforeunload', this.handleUnload);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.handleUnload);
}
...
}
Please advice, and thanks.