Class not changing on scroll in react - javascript

Following is my handleScroll function in which I am trying to add red class if it scroll down to a certain limit else apply blue. However this is only going inside the else statement and also console.log(e.target.scrollTop); its consoling as undefined. Let me know what I am doing wrong here.
Code -
handleScroll(e) {
console.log(e.target.scrollTop);
if (window.screenX > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Codesandbox - https://codesandbox.io/s/silly-feynman-m6hp1

I would highly recommend adding an extra check to your condition. When you scroll a single-time, you update your component-state multiple times after a certain range (100), which is unnecessary. You only need to update it once.
It will keep updating because you meet the condition inside handleScroll, even though the background has already changed. The sheer amount of updates can cause your app to crash.
Try something like this it will update your component-state as expected and only when necessary: https://codesandbox.io/s/white-architecture-jepyc
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
navBckgroundColor: `blue`,
altered: false
};
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
//use arrow function instead so that its "this" keyword points to the component's execution context. Otherwise, "this" will point to the global window object where you cannot use this.setState.
handleScroll = e => {
if (window.pageYOffset > 100 && !this.state.altered) {
this.setState({
navBckgroundColor: `red`,
altered: true
});
} else if(window.pageYOffset < 100 && this.state.altered) {
this.setState({
navBckgroundColor: `blue`,
altered: false
});
}
};
render() {
return (
<div className="App">
<Navbar bckGroundColor={this.state.navBckgroundColor} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}

Use window.scrollY instead of window.screenY and also bind the handleScroll method.
handleScroll = (e) => {
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Working demo

Please use
handleScroll = e => {
console.log(e.target.scrollTop);
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Please see the workable code on :
https://codesandbox.io/s/friendly-swirles-bwl06
also your window.screenX will always output the same value, and thus no change to the colors.
I have changed that in the code as well

Related

Confused about how to structure my React program with event listeners

I'm building a board game in React, and I want players to be able to move pieces using arrow keys. Here's how my code is currently structured:
class Game extends React.Component {
constructor(props) {
...
};
move(direction) {
...
this.setState({
...
});
};
render() {
return (
<div>
<div>
<Board
board={...}
/>
</div>
{document.addEventListener('keyup', event => {
event.preventDefault();
if (event.code === 'ArrowUp') {
this.move("u");
} else if (event.code === 'ArrowRight') {
this.move("r");
} else if (event.code === 'ArrowDown') {
this.move("d");
} else if (event.code === 'ArrowLeft') {
this.move("l");
}
})}
</div>
);
}
}
const App = () => {
return (
<div>
<div className="main">
<Game />
</div>
</div>
);
};
Here's the issue: each time move() gets called, the Game component refreshes since the state is updated (which is what I want). However, refreshing the component adds another event listener to the page. The next time I press an arrow key, move() is called twice. After that, four times (and so on).
How can I restructure my code so that the event listener only gets added once? I need move() to have access to the Game class's state. Thanks!

JS Scroll event efficiency

Hi everyone I would like to know when my user has scroll bellow 220px. I've created a event who trigger correctly my useState when the user scrolled below 220px
window.addEventListener("scroll", function () {
if (this.window.scrollY > 200 && isScroll === false) {
console.log("bon");
setIsScroll(true);
} else if (this.window.scrollY < 200 && isScroll === true) {
console.log("fini");
setIsScroll(false);
}
});
my question is: Is their a better way to do it because when I scrool only a little I've got a lot of instruction. And Will it be a problem on my application efficiency?
MAJOR performance issue due to the event listener not being wrapped in a useEffect with a remove event listener as the return.
You're just piling up event listeners every time the component rerenders. Which is why you're seeing tens of thousands of console.logs after just a few scrolls.
const [isScroll, setIsScroll] = useState(false);
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 200 && isScroll === false) {
console.log("bon");
setIsScroll(true);
} else if (window.scrollY < 200 && isScroll === true) {
console.log("fini");
setIsScroll(false);
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [isScroll]);
Above solution solves this problem. Note the isScroll parameter being passed to the useEffect dependency array in order to update the handleScroll function with the current state.

How could I see if my scroll reached a ref ? React

I'm building a navbar who changes its background color if the user has scrolled until an ad.
Before, I used the method "window.scrollY" and change the color if its number is over 700.
Unfortunatly this method is bad to be responsive with differents screens.
In my navabr when I click on a title, the website automaticaly scroll until the part.
I used references to do it and I would like to know if you know a way to check if the scroll reached a reference in the top of the screen (to replace the bad color system).
Like ref.current.isReached ? return a boolean.
With that I could change my css in a better way.
I hope I was clear, my english is very bad.
This is my classe with the ref system:
const Home = ({ section }: Props) => {
const sectionRef1: any = useRef(React.createRef());
const sectionRef2: any = useRef(React.createRef());
const scroll = (ref: any) => {
ref.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
};
function scrollToSection(section: number) {
if (section === 1) {
scroll(sectionRef1);
}
else if (section === 2) {
scroll(sectionRef2);
}
else if (section === 3) {
//TODO: active button
}
}
useEffect(() => {
scrollToSection(section);
}, [section]);
return (
<div>
<div ref={sectionRef1} />
<Carrousel></Carrousel>
<div ref={sectionRef2} className="margin_top_portrait" />
<Portrait></Portrait>
</div>
);
}
export default Home;
Thanks in advance
useEffect(() => {
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, []);
In onScroll function you can find out whatever your want about your element position with ref.current.getBoundingClientRect()
Example of checking if element is in viewport

Checkbox becomes unclickable after checkbox limit function occur reactjs

I have a mapping function which shows JSON values into checkboxes, each checkbox triggers another 2 checkboxes, the JSON I am using have a min/max value which I made a function for to set min/max for checkbox selections in each section. My problem is that once the parent & child checkboxes are clicked and then I redo the process where I click it to shrink it and click it again to expand it, the children checkboxes stops being clickable.
The checkbox values are passed as props from Checkbox.js to Itemlist.js where the fetch/map happens.
React Live Snippet: https://codesandbox.io/embed/2178pwz6or?fontsize=14
Checkbox.js
class Checkboxes extends React.Component {
constructor(props) {
super(props);
this.state = {
currentData: 0,
limit: 2,
checked: false
};
}
selectData(id, event) {
let isSelected = event.currentTarget.checked;
if (isSelected) {
if (this.state.currentData < this.props.max) {
this.setState({ currentData: this.state.currentData + 1 });
} else {
event.preventDefault();
event.currentTarget.checked = false;
}
} else {
if (this.state.currentData > this.props.min) {
this.setState({ currentData: this.state.currentData - 1 });
} else {
event.preventDefault();
event.currentTarget.checked = true;
}
}
}
render() {
const input2Checkboxes =
this.props.options &&
this.props.options.map(item => {
return (
<div className="inputGroup2">
{" "}
<div className="inputGroup">
<input
id={this.props.childk + (item.name || item.description)}
name="checkbox"
type="checkbox"
onChange={this.selectData.bind(
this,
this.props.childk + (item.name || item.description)
)}
/>
<label
htmlFor={this.props.childk + (item.name || item.description)}
>
{item.name || item.description}{" "}
{/** <img src={this.props.img} alt="" /> <span className="pricemod">{props.childprice} SAR</span>
*/}
</label>
</div>
</div>
);
});
return (
<form className="form">
<div>
{/** <h2>{this.props.title}</h2>*/}
<div className="inputGroup">
<input
id={this.props.childk + this.props.name}
name="checkbox"
type="checkbox"
checked={this.state.checked}
onChange={this.selectData.bind(
this,
this.props.childk + this.props.uniq
)}
onChange={() => {
this.setState({ checked: !this.state.checked });
}}
/>
<label htmlFor={this.props.childk + this.props.name}>
{this.props.name}{" "}
</label>
</div>{" "}
{this.state.checked ? input2Checkboxes : undefined}
</div>
</form>
);
}
}
export default Checkboxes;
In your code sandbox snippet, just change line 24 in Checkbox.js component to this
if (this.state.currentData >= this.props.min) {
To help you see why you're encountering the issue, you can modify the selectData method of Checkbox.js with some helpful debugging statements:
// Checkbox.js
selectData(id, event) {
let isSelected = event.currentTarget.checked;
console.log(
`Attempting to ${isSelected ? "check" : "uncheck"} ${
event.currentTarget.id
}`
);
console.log(`min is ${this.props.min}, max is ${this.props.max}`);
console.log(`currentData is: ${this.state.currentData}`);
if (isSelected) {
if (this.state.currentData < this.props.max) {
console.log('Allowed to check. Incrementing currentData')
this.setState({ currentData: this.state.currentData + 1 });
} else {
console.log('Not allowed to check: greater than or equal to max')
event.preventDefault();
event.currentTarget.checked = false;
}
} else {
if (this.state.currentData > this.props.min) {
console.log('Allowed to uncheck. Decrementing currentData')
this.setState({ currentData: this.state.currentData - 1 });
} else {
console.log('Not allowed to uncheck. Less than or equal to min')
// event.preventDefault();
// event.currentTarget.checked = true;
}
}
}
You'll notice, as you check "Side 1" and then un-check it, that you do not satisfy the condition if (this.state.currentData > this.props.min), and so you never get to actually decrement this.state.currentData.
To fix this, you need to do one of 2 things:
Change your conditional to be if (this.state.currentData >= this.props.min)
OR
Change your data.json to set your min to 0 rather than 1.
UPDATE
You also have an issue when you do the following steps:
Check a side collection (reveals sides)
Check a side (increments currentData)
Uncheck the side collection (hides sides)
Check the side collection again
At this point, your sides are revealed, but they are unchecked
AND your currentData is already 1.
You cannot check any sides because of this.
To fix this, you can either:
Reset currentData to 0 whenever the side collection is checked/un-checked.
OR
Change your input2Checkboxes method to take into account which sides have been checked and keep their states, even if the side collection gets unchecked.
The first of the above 2 options is simpler to do. Within the render method of Checkbox.js, you need your side collection input's onChange to look like this:
// Checkbox.js
onChange={() => {
this.setState({
checked: !this.state.checked,
currentData: 0
})
}}
I've updated the forked code sandbox to show the working demo now:

Adding a className on scroll?

How can I add a className when the page scrolls? I have ready many other articles and answers to this (may be a duplicate) but none have helped me understand what is wrong with my code below.
If the code is not the issue I believe that it stems from a perspective wrapper around the app that may disallow the registration of scroll. How can I add the event listener to register scroll on id=container
constructor(props) {
super(props)
this.state = {
isStuck: true,
}
this.handleHeaderStuck = this.handleHeaderStuck.bind(this)
}
componentDidMount () {
window.addEventListener('scroll', this.handleHeaderStuck);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleHeaderStuck);
}
handleHeaderStuck() {
if (window.scrollY === 0 && this.state.isStuck === true) {
this.setState({isStuck: false});
}
else if (window.scrollY !== 0 && this.state.isStuck !== true) {
this.setState({isStuck: true});
}
}
render() {
return (
<main className={this.state.isStuck ? 'header-stuck' : ''}>
...
</main>
This screenshot reassures me that the issue is with the registering of onScroll listener
Be sure your component have enough height for scroll. Your code works.
Add some height to main and check it.
main {
height: 2000px;
}
https://jsfiddle.net/69z2wepo/156204/
You code has an issue, onScroll is attached a listener function handleScroll whereas the function is handleHeaderStuck in your case. Change the listener to execute the correct function.
componentDidMount () {
window.addEventListener('scroll', this.handleHeaderStuck);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleHeaderStuck);
}

Categories