We recently worked on an auto-scrolling while freely swipeable component using React.js. The implementation idea is inspired by this article
And we've made something like this in React:
import React, { Component } from "react";
import PropTypes from "prop-types";
import "./AutoScroller.css";
const NUM_OF_CLONES = 10;
const AUTO_SCROLL_OFFSET = 1; // min offset of scrollTo is 1
const AUTO_SCROLL_INTERVAL = 32; // 1000 ms / 30 fps
export default class AutoScroller extends Component {
static propTypes = {
contents: PropTypes.array.isRequired,
itemWidth: PropTypes.number.isRequired,
numsOfItemsPerScreen: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.autoScrollerRef = React.createRef();
this.currentPosition = 0;
this.autoScrollTimer = null;
this.scrollingTimer = null;
/* boolean status */
this.isTouch = false;
this.isScrolling = false;
}
componentDidMount() {
this.startAutoScroll();
this.autoScrollerRef.current.addEventListener(
"touchstart",
this.touchStartHandler
);
this.autoScrollerRef.current.addEventListener(
"touchend",
this.touchEndHandler
);
this.autoScrollerRef.current.addEventListener("scroll", this.scrollHandler);
this.autoScrollerRef.current.addEventListener(
"contextmenu",
this.contextMenuHandler
);
}
componentWillUnmount() {
this.clearAutoScroll();
this.clearScrollingTimer();
this.autoScrollerRef.current.removeEventListener(
"touchstart",
this.touchStartHandler
);
this.autoScrollerRef.current.removeEventListener(
"touchend",
this.touchEndHandler
);
this.autoScrollerRef.current.removeEventListener(
"scroll",
this.scrollHandler
);
this.autoScrollerRef.current.removeEventListener(
"contextmenu",
this.contextMenuHandler
);
}
touchStartHandler = () => {
this.isTouch = true;
this.clearAutoScroll();
};
touchEndHandler = () => {
this.isTouch = false;
if (!this.isScrolling) {
this.currentPosition = this.autoScrollerRef.current.scrollLeft;
this.startAutoScroll();
}
};
scrollHandler = () => {
const {
contents: { length },
itemWidth
} = this.props;
this.isScrolling = true;
this.currentPosition = this.autoScrollerRef.current.scrollLeft;
const maxOffset = length * itemWidth;
if (this.currentPosition > maxOffset) {
const offset = this.currentPosition - maxOffset;
this.autoScrollerRef.current.scrollTo(offset, 0);
this.currentPosition = offset;
} else if (this.currentPosition <= 0) {
const offset = this.currentPosition + maxOffset;
this.autoScrollerRef.current.scrollTo(offset, 0);
this.currentPosition = offset;
}
/***
* note: there will be only one timer, and the timer is only created by the very last scroll
* only when the scroll event is not triggered anymore, the timer starts to get executed.
*/
if (this.scrollingTimer) {
clearTimeout(this.scrollingTimer);
}
this.scrollingTimer = setTimeout(() => {
this.isScrolling = false;
/***
* note: resume auto-scroll when the momentum scroll (after finger leaves) stalls the scroll
*/
if (!this.isTouch) {
this.startAutoScroll();
}
}, 300);
};
contextMenuHandler = (event) => {
event.preventDefault();
};
startAutoScroll = () => {
if (!this.autoScrollTimer) {
this.autoScrollTimer = setInterval(this.autoScroll, AUTO_SCROLL_INTERVAL);
}
};
clearAutoScroll = () => {
if (this.autoScrollTimer) {
clearInterval(this.autoScrollTimer);
this.autoScrollTimer = null;
}
};
clearScrollingTimer = () => {
if (this.scrollingTimer) {
clearTimeout(this.scrollingTimer);
this.scrollingTimer = null;
}
};
autoScroll = () => {
const {
contents: { length },
itemWidth,
numsOfItemsPerScreen
} = this.props;
if (this.currentPosition < 0) {
this.currentPosition = 0;
}
if (length > numsOfItemsPerScreen) {
const position = this.currentPosition + AUTO_SCROLL_OFFSET;
this.autoScrollerRef.current.scrollTo(position, 0);
const maxOffset = length * itemWidth;
if (this.currentPosition > maxOffset) {
const offset = this.currentPosition - maxOffset;
this.autoScrollerRef.current.scrollTo(offset, 0);
this.currentPosition = offset;
} else {
this.currentPosition = position;
}
}
};
getWrappedData = () => {
const { contents } = this.props;
const { length } = contents;
const numberOfClones = length < NUM_OF_CLONES ? length : NUM_OF_CLONES;
return [...contents, ...contents.slice(0, numberOfClones)];
};
render() {
const { itemGap, lineHeight } = this.props;
return (
<div className="auto-scroller" ref={this.autoScrollerRef}>
<ul>
{this.getWrappedData().map((content, index) => (
<Item
key={`auto-scroller-item-${index}`}
content={content}
itemGap={itemGap}
lineHeight={lineHeight}
/>
))}
</ul>
</div>
);
}
}
class Item extends Component {
static propTypes = {
content: PropTypes.object.isRequired,
itemGap: PropTypes.number,
lineHeight: PropTypes.number
};
render() {
const { content, itemGap = 10 } = this.props;
return (
<li
className="auto-scroller__item"
style={{ paddingRight: `${itemGap}px` }}
>
<div className="auto-scroller__item__content">
<img draggable={false} src={content.imgUrl} />
<div className="auto-scroller__item__content__title">
{content.title}
</div>
</div>
</li>
);
}
}
You can test with the demo from PlayCode (source code).
Just open the link with Safari on the iPhone.
What I observed was every time when it was on the boundary cases, the image started to flicker.
Further, if you swipe it with your finger forth and back on that point, the whole UI started to flicker. (see this screen recording) However, we didn't spot this glitch on Android devices.
Any possible solutions are welcome. Does anyone encounter something like this before?
removing overflow-y: hidden; and overflow-x: auto; from autoscroller.css
solved it on my end.
another solution would be to add z-index: 1; and scroll-behavior: smooth; to .auto-scroller
let me know if it worked!
Related
I am trying to retain the scroll position on back button but the scroll position is going back to the 0,0
The code is as follows -
<SearchListWrapper className="TestSL" ref={this.myRef} onScroll={this.handleScrollPosition} >
handleScrollPosition(e){
sessionStorage.setItem("scrollPosition", this.myRef.current!.scrollTop.toString());
};
async componentDidMount() {
console.log("inside CDM")
if(sessionStorage.getItem("scrollPosition"))
{
const scrollpos =Number(sessionStorage.getItem("scrollPosition"))
this.myRef.current!.scrollTo(0,scrollpos)
}
}
I tried the above code and expecting the values in session to be fixed and not change back to 0,0 on back button.
Here's a solution for you:
example-child-scroll.jsx
// This example will manage the scroll position for a DOM element.
// The key must be unique per managed element, as it is used as a key in a store
// when saving and restoring the position.
import React from 'react'
import ScrollManager from './ScrollManager'
export default function App() {
return (
<div>
<ScrollManager scrollKey="some-list-key">
{({ connectScrollTarget, ...props }) =>
<div ref={connectScrollTarget} style={{ overflow: 'auto', maxHeight: 500 }}>
If this div is unmounted and remounted, scroll position is restored.
</div>
}
</ScrollManager>
</div>
)
}
ScrollManager.jsx
import React from 'react'
import requestAnimationFrame from 'raf'
export const memoryStore = {
_data: new Map(),
get(key) {
if (!key) {
return null
}
return this._data.get(key) || null
},
set(key, data) {
if (!key) {
return
}
return this._data.set(key, data)
}
}
/**
* Component that will save and restore Window scroll position.
*/
export default class ScrollPositionManager extends React.Component {
constructor(props) {
super(...arguments)
this.connectScrollTarget = this.connectScrollTarget.bind(this)
this._target = window
}
connectScrollTarget(node) {
this._target = node
}
restoreScrollPosition(pos) {
pos = pos || this.props.scrollStore.get(this.props.scrollKey)
if (this._target && pos) {
requestAnimationFrame(() => {
scroll(this._target, pos.x, pos.y)
})
}
}
saveScrollPosition(key) {
if (this._target) {
const pos = getScrollPosition(this._target)
key = key || this.props.scrollKey
this.props.scrollStore.set(key, pos)
}
}
componentDidMount() {
this.restoreScrollPosition()
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (this.props.scrollKey !== nextProps.scrollKey) {
this.saveScrollPosition()
}
return true;
}
componentDidUpdate(prevProps) {
if (this.props.scrollKey !== prevProps.scrollKey) {
this.restoreScrollPosition()
}
}
componentWillUnmount() {
this.saveScrollPosition()
}
render() {
const { children = null, ...props } = this.props
return (
children &&
children({ ...props, connectScrollTarget: this.connectScrollTarget })
)
}
}
ScrollPositionManager.defaultProps = {
scrollStore: memoryStore
}
function scroll(target, x, y) {
if (target instanceof window.Window) {
target.scrollTo(x, y)
} else {
target.scrollLeft = x
target.scrollTop = y
}
}
function getScrollPosition(target) {
if (target instanceof window.Window) {
return { x: target.scrollX, y: target.scrollY }
}
return { x: target.scrollLeft, y: target.scrollTop }
}
I'm trying to rewrite the App component in this CodePen as a Functional component using Typescript.
However, I am getting error like this when trying to run it:
ERROR in src/App.tsx:13:14
TS2339: Property 'forceUpdateHandler' does not exist on type 'MutableRefObject<Spinner | null>'.
11 |
12 | const handleClick = () => {
> 13 | _child1?.forceUpdateHandler();
| ^^^^^^^^^^^^^^^^^^
14 | _child2?.forceUpdateHandler();
15 | _child3?.forceUpdateHandler();
16 | };
What is the correct way to handle Spinner.forceUpdateHandler?
Here's my attempt:
App.tsx
Rewriting the class component as a functional component
This has been simplified from the original to focus on the problematic area
import React, { useRef } from "react";
import "./App.css";
import Spinner from "./Spinner.js";
const App = () => {
const [matches, setMatches] = React.useState<number[]>([]);
const _child1 = useRef<Spinner | null>(null);
const _child2 = useRef<Spinner | null>(null);
const _child3 = useRef<Spinner | null>(null);
const handleClick = () => {
_child1?.forceUpdateHandler();
_child2?.forceUpdateHandler();
_child3?.forceUpdateHandler();
};
const finishHandler = (value: number) => {
setMatches([...matches, value]);
if (matches.length === 3) {
console.log("Done");
emptyArray();
}
};
const emptyArray = () => {
setMatches([]);
};
return (
<div>
<div className={`spinner-container`}>
<Spinner onFinish={finishHandler} ref={_child1} timer="1000" />
<Spinner onFinish={finishHandler} ref={_child2} timer="1400" />
<Spinner onFinish={finishHandler} ref={_child3} timer="2200" />
<div className="gradient-fade"></div>
</div>
<button onClick={handleClick}>SPIN!!!</button>
</div>
);
};
export default App;
Spinner.js
Same as in the above CodePen, with imports and exports added
import React from "react";
class Spinner extends React.Component {
constructor(props) {
super(props);
this.forceUpdateHandler = this.forceUpdateHandler.bind(this);
}
forceUpdateHandler() {
this.reset();
}
reset() {
if (this.timer) {
clearInterval(this.timer);
}
this.start = this.setStartPosition();
this.setState({
position: this.start,
timeRemaining: this.props.timer,
});
this.timer = setInterval(() => {
this.tick();
}, 100);
}
state = {
position: 0,
lastPosition: null,
};
static iconHeight = 188;
multiplier = Math.floor(Math.random() * (4 - 1) + 1);
start = this.setStartPosition();
speed = Spinner.iconHeight * this.multiplier;
setStartPosition() {
return Math.floor(Math.random() * 9) * Spinner.iconHeight * -1;
}
moveBackground() {
this.setState({
position: this.state.position - this.speed,
timeRemaining: this.state.timeRemaining - 100,
});
}
getSymbolFromPosition() {
let { position } = this.state;
const totalSymbols = 9;
const maxPosition = Spinner.iconHeight * (totalSymbols - 1) * -1;
let moved = (this.props.timer / 100) * this.multiplier;
let startPosition = this.start;
let currentPosition = startPosition;
for (let i = 0; i < moved; i++) {
currentPosition -= Spinner.iconHeight;
if (currentPosition < maxPosition) {
currentPosition = 0;
}
}
this.props.onFinish(currentPosition);
}
tick() {
if (this.state.timeRemaining <= 0) {
clearInterval(this.timer);
this.getSymbolFromPosition();
} else {
this.moveBackground();
}
}
componentDidMount() {
clearInterval(this.timer);
this.setState({
position: this.start,
timeRemaining: this.props.timer,
});
this.timer = setInterval(() => {
this.tick();
}, 100);
}
render() {
let { position, current } = this.state;
return (
<div
style={{ backgroundPosition: "0px " + position + "px" }}
className={`icons`}
/>
);
}
}
export default Spinner;
Ref's hold the actual reference in the current property, so it should actually be:
const handleClick = () => {
_child1?.current?.forceUpdateHandler();
_child2?.current?.forceUpdateHandler();
_child3?.current?.forceUpdateHandler();
};
You can read more about it here
just an idea may be you should try redefining the Spinner type in your functional component
type SpinnerProps ={
forceUpdateHandler: () => void,
startPosition: () => number,
.... // TODO all the other props need to be defined
}
const _child1 = useRef<SpinnerProps | null>(null);
I'm trying to modify a Spinner component by adding a new method Spinner.moveToPosition so that it will spin to a specific symbol, as opposed to Spinner.forceUpdateHandler which spins it to a random symbol.
Here's my CodePen: https://codepen.io/gameveloster/pen/qBxJPVK
Original CodePen: https://codepen.io/antibland/pen/ypagZd
However, when I added a "SPIN!" button where clicking the button will get them to spin to positions 0, 0 and 1 so that the left two spinners will display the same image
const handleClick = () => {
// The first and second symbols should be the same after spinning, but they are not
_child1?.current?.moveToPosition(0);
_child2?.current?.moveToPosition(0);
_child3?.current?.moveToPosition(1);
};
they end up not doing that:
What might be the issue here? Thank you!
Spinner.js
import React from "react";
class Spinner extends React.Component {
constructor(props) {
super(props);
this.forceUpdateHandler = this.forceUpdateHandler.bind(this);
}
moveToPosition(position) {
if (this.timer) {
clearInterval(this.timer);
}
this.setState({
position: position * Spinner.iconHeight,
timeRemaining: this.props.timer,
});
this.timer = setInterval(() => {
this.tick();
}, 100);
}
forceUpdateHandler() {
this.reset();
}
reset() {
if (this.timer) {
clearInterval(this.timer);
}
this.start = this.setStartPosition();
this.setState({
position: this.start,
timeRemaining: this.props.timer,
});
this.timer = setInterval(() => {
this.tick();
}, 100);
}
state = {
position: 0,
lastPosition: null,
};
static iconHeight = 188;
multiplier = Math.floor(Math.random() * (4 - 1) + 1);
start = this.setStartPosition();
speed = Spinner.iconHeight * this.multiplier;
setStartPosition() {
return Math.floor(Math.random() * 9) * Spinner.iconHeight * -1;
}
moveBackground() {
this.setState({
position: this.state.position - this.speed,
timeRemaining: this.state.timeRemaining - 100,
});
}
getSymbolFromPosition() {
let { position } = this.state;
const totalSymbols = 9;
const maxPosition = Spinner.iconHeight * (totalSymbols - 1) * -1;
let moved = (this.props.timer / 100) * this.multiplier;
let startPosition = this.start;
let currentPosition = startPosition;
for (let i = 0; i < moved; i++) {
currentPosition -= Spinner.iconHeight;
if (currentPosition < maxPosition) {
currentPosition = 0;
}
}
this.props.onFinish(currentPosition);
}
tick() {
if (this.state.timeRemaining <= 0) {
clearInterval(this.timer);
this.getSymbolFromPosition();
} else {
this.moveBackground();
}
}
componentDidMount() {
clearInterval(this.timer);
this.setState({
position: this.start,
timeRemaining: this.props.timer,
});
this.timer = setInterval(() => {
this.tick();
}, 100);
}
render() {
let { position, current } = this.state;
return (
<div
style={{ backgroundPosition: "0px " + position + "px" }}
className={`icons`}
/>
);
}
}
export default Spinner;
App.tsx
import React, { useRef } from "react";
import "./App.css";
import Spinner from "./Spinner.js";
const App = () => {
const [matches, setMatches] = React.useState<number[]>([]);
const _child1 = useRef<Spinner | null>(null);
const _child2 = useRef<Spinner | null>(null);
const _child3 = useRef<Spinner | null>(null);
const handleClick = () => {
// The first and second symbols should be the same after spinning, but they are not
_child1?.current?.moveToPosition(0);
_child2?.current?.moveToPosition(0);
_child3?.current?.moveToPosition(1);
};
const finishHandler = (value: number) => {
setMatches([...matches, value]);
if (matches.length === 3) {
console.log("Done");
emptyArray();
}
};
const emptyArray = () => {
setMatches([]);
};
return (
<div>
<div className={`spinner-container`}>
<Spinner onFinish={finishHandler} ref={_child1} timer="1000" />
<Spinner onFinish={finishHandler} ref={_child2} timer="1400" />
<Spinner onFinish={finishHandler} ref={_child3} timer="2200" />
<div className="gradient-fade"></div>
</div>
<button onClick={handleClick}>SPIN!!!</button>
</div>
);
};
export default App;
I'm trying to receive fog effect in React. The main idea is that I have two components: first component handles with updating of coordinates of clouds and their velocity, the second component is responsible for one cloud. I have problem with moving of clouds, if I don't clear canvas I can see track of every cloud, if I apply canvas.clear I can't see anything. Do you have any tip, where I should place clear canvas.clear or do you have other ideas?
The first component:
import React from 'react';
import styled from 'styled-components';
import FogPiece from './Fog-Piece.jsx';
const CanvasContext = React.createContext();
const FogDiv = styled.div`
position: absolute;
width:100vw;
height:100vh;
`
class Fog extends React.Component{
constructor(props){
super(props);
this.canvas = React.createRef();
this.state = {
ctx: null,
parameters:[],
screenWidth : 0,
screenHeight: 0,
}
}
componentDidMount = () => {
Promise.all(this.newCoordinates()).then((paramArray) =>{
this.setState({
ctx: this.canvas.current.getContext('2d'),
screenWidth: this.canvas.current.parentNode.getBoundingClientRect().width,
screenHeight: this.canvas.current.parentNode.getBoundingClientRect().height,
parameters: paramArray
});
window.requestAnimationFrame(this.update)
})
}
newCoordinates = () => {
return(Array.from(Array(this.props.density).keys()).map(elem =>{
return new Promise (resolve => {
const params = {
x: this.random(0,this.state.screenWidth),
y: this.random(0,this.state.screenHeight),
velocityX: this.random(-this.props.maxVelocity, this.props.maxVelocity),
velocityY: this.random(-this.props.maxVelocity, this.props.maxVelocity)
}
resolve(params)
})
}))
}
updateCoordinates = () => {
return(this.state.parameters.map(elem =>{
return new Promise (resolve => {
elem = this.ifCross(elem.x, elem.y, elem.velocityX, elem.velocityY);
const params = {
x: elem.x + elem.velocityX,
y: elem.y + elem.velocityY,
velocityX: elem.velocityX,
velocityY: elem.velocityY
}
resolve(params)
})
}))
}
random = (min,max) => {
return Math.random()*(max - min) + min
}
ifCross = (x,y, velocityX, velocityY) => {
if (x > this.state.screenWidth){
x = this.state.screenWidth
velocityX = - velocityX
}
if (x < 0){
x = 0
velocityX = - velocityX
}
if (y > this.state.screenHeight){
y = this.state.screenHeight
velocityY = - velocityY
}
if (y < 0){
y = 0
velocityY = - velocityY
}
return {x:x, y:y, velocityX:velocityX, velocityY:velocityY }
}
update = () => {
Promise.all(this.updateCoordinates()).then((paramArray) =>{
//here is the problem
// this.state.ctx.clearRect(0,0,this.state.screenWidth, this.state.screenHeight)
this.setState({
parameters: paramArray,
});
window.requestAnimationFrame(this.update)
})
}
render(){
return(
<FogDiv>
<canvas width={this.state.screenWidth} height={this.state.screenHeight} ref = {this.canvas} >
{this.state.ctx && (
<CanvasContext.Provider value = {this.state.ctx}>
{this.state.parameters.map(param =>(
<FogPiece
x = {param.x}
y = {param.y}
/>
))}
</CanvasContext.Provider>
)}
</canvas>
</FogDiv>
)
}
}
export default Fog;
export {
CanvasContext
}
the second one:
import React from 'react';
import styled from 'styled-components';
import {CanvasContext} from './Fog.jsx';
class FogPiece extends React.Component{
constructor(props){
super(props);
this.state = {
image:'https://media.istockphoto.com/vectors/sample-red-square-grunge-textured-isolated-stamp-vector-id471401412',
}
}
random(min,max){
return Math.random()*(max - min) + min
}
render(){
return(
<CanvasContext.Consumer>
{ctx => {
console.log("x", "y", this.props)
const img = new Image();
img.src = this.state.image;
img.onload = () => {
ctx.drawImage(img,
this.props.x,
this.props.y,
40,
40)
}
}}
</CanvasContext.Consumer>
)
}
}
export default FogPiece;
I have put together a infinite scroll to load more items when a user reaches the bottom of the screen. It works fine the first time but for some reason the eventListener seems to disappear when the first nextLink from redux is loaded.
My code:
import React, { Component } from "react";
import { connect } from "react-redux";
import * as teamsActions from "../../store/teams/actions";
import TeamCard from "../../common/teamCard/teamCard";
import ReactAI from "react-appinsights";
import WithLoading from "../../common/utils/withLoading";
import {
objectToArray,
sortArray
} from "../../assets/general_functions/objectsAndArrays";
import { faRubleSign } from "#fortawesome/free-solid-svg-icons";
class TeamsContainer extends Component {
_isMounted = false;
state = {
scrolling: false
};
componentDidMount() {
this._isMounted = true;
this.props.onFetchTeams();
this.scrollListener = window.addEventListener("scroll", this.handleScroll);
}
s;
componentWillUnmount() {
this._isMounted = false;
window.removeEventListener("scroll", this.handleScroll);
}
loadTeams = () => {
console.log("is teams loading?", this.props.loading);
if (this.props.loading === false) {
console.log("What is the nextLink", this.props.teams["#odata.nextLink"]);
this.props.onFetchMoreTeams(this.props.teams["#odata.nextLink"]);
}
};
loadMore = () => {
this.setState(
{
scrolling: true
},
this.loadTeams
);
};
handleScroll = () => {
const { scrolling } = this.state;
if (scrolling) return;
if (
typeof this.props.teams.value !== "undefined" ||
this.props.teams.value > 0
) {
console.log("value", this.props.teams.value);
const lastTeam = document.querySelector(
".team-card-wrapper:last-of-type"
);
// get the height of the current team, and get the height of the current position on screen.
const lastTeamOffset = lastTeam.offsetTop + lastTeam.clientHeight;
const pageOffset = window.pageYOffset + window.innerHeight;
const bottomOffset = 30;
if (pageOffset > lastTeamOffset - bottomOffset) {
this.loadMore();
}
}
};
render() {
let loading = "";
let error = "";
let teams = [];
let delay = 0;
let loadMoreButton = "";
// check whether the component is fetching data
let loader = "";
if (this.props.teamsLoading) {
loader = <WithLoading isLoading={true} />;
}
// check if there was an error
this.props.error && this.props.loading === false
? (error = <p>There was an error</p>)
: (error = "");
// reorder the teams and make teamCards of it.
if (this.props.teams["value"]) {
// convert the teams object to an array of objects.
// order it by sequence property.
teams = this.props.teams.value;
teams = objectToArray(this.props.teams["value"]);
teams = teams.sort(sortArray("sequence")).reverse();
teams = teams.map(team => {
if (delay === 300) {
delay = 0;
}
delay = delay + 75;
return (
<TeamCard
delay={delay}
id={team.id}
title={team.title}
description={team.description}
isFavorite={team.isFavorite}
memberCount={team.memberCount}
key={team.id}
/>
);
});
} else {
teams = loader = <WithLoading isLoading={true} />;
}
// this.props.teams["value"]);
return (
<React.Fragment>
<div className="App">
{loader == "" ? (
<div className="team-cards-wrapper">{teams}</div>
) : (
<div>{loader}</div>
)}
</div>
</React.Fragment>
);
}
}
const mapStateToProps = state => {
return {
error: state.teamsSlice.teamsError,
loading: state.teamsSlice.teamsLoading,
teams: state.teamsSlice.teams,
searchTerm: state.teamsSlice.searchTerm
};
};
const mapDispatchToProps = dispatch => {
return {
onFetchTeams: () => dispatch(teamsActions.fetchTeams()),
onFetchMoreTeams: teamsNextLink =>
dispatch(teamsActions.fetchMoreTeams(teamsNextLink))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ReactAI.withTracking(TeamsContainer));
My console (The value is printed on scroll. after loading it, it doesnt echo any value anymore):
Event handlers according to the console:
Redux store:
Edit:
I found that there was a problem in the state.scrolling. It's set to true but never reset after the data has loaded.
any help is much appreciated! Cheers
I think because you are having two componentWillUnMount. and this line
window.addEventListener("scroll", e => {
this.handleScroll(e);
});
seems to be a little wrong. it maybe window.addEventListener("scroll", this.handleScroll) only
- second thing is that I think you should use debounce on scroll event so it can be better for performance
I found out that the problem was setting the scrolling state back to false.
After fetching more redux items, i decided to use a timeout to debounce incoming similar api requests.
setTimeout(() => {
this.setState({ scrolling: false });
}, 300);