React Noob - thought I'd ask here for a quick answer before I spend hours digging.
In the example below I have an event listener inside useEffect that listens for scroll position on a container and fires a trigger after a point if it hasn't done so already.
I want to know why the event still triggers even though the boolean registers true in the DOM. I've solved the problem by using a normal variable but I think it would benefit me to understand why this is happening. I've read lightly into mutating states and have experimented with changing the useState to an object like useState({status: false}) but this had similar results.
Even a point in the direction of a reading topic would be enough. Cheers!
const App = (props) => {
var [stateBool, setStateBool] = React.useState(false);
var nonStateBool = false;
var containerRef = React.useRef(null);
React.useEffect(() => {
containerRef.current.addEventListener('scroll', (event) => {
var cont = containerRef.current;
var triggerPoint =
cont.scrollWidth - cont.clientWidth - cont.scrollWidth * 0.2;
var scrollPos = cont.scrollLeft;
var triggerEl = document.getElementById('vr');
if (triggerEl) {
triggerEl.style.left = triggerPoint 'px';
}
if (scrollPos > triggerPoint && stateBool === false) {
console.log('triggered', nonStateBool, stateBool);
setStateBool(true);
}
});
}, [containerRef.current, stateBool]);
return (
<div>
<div ref={containerRef} id='container'>
<div className='divElement'>
inner element <vr id='vr'></vr>
</div>
</div>
<ul>
<li> nonStateBool {nonStateBool ? 'true' : 'false'}</li>
<li> State Bool {stateBool ? 'true' : 'false'}</li>
</ul>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
#container {
width: 200px;
background: grey;
padding: 1em;
overflow: hidden;
overflow-x: scroll;
}
.divElement {
width: 1500px;
height: 50px;
background: red;
}
.divElement vr {
border: 1px solid white;
position: relative;
height: 100%;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
CodePudding user response:
You have:
React.useEffect(() => {
containerRef.current.addEventListener('scroll', (event) => {
// ...
});
}, [containerRef.current, stateBool]);
So every time stateBool
changes, you call addEventListener
- adding a new scroll handler. For the more predictable output you're expecting, remove the previous scroll handler in the effect cleanup.
React.useEffect(() => {
const handler = (event) => {
// ...
};
containerRef.current.addEventListener('scroll', handler);
return () => containerRef.current.removeEventListener('scroll', handler);
}, [containerRef.current, stateBool]);
const App = (props) => {
var [stateBool, setStateBool] = React.useState(false);
var nonStateBool = false;
var containerRef = React.useRef(null);
React.useEffect(() => {
const handler = (event) => {
var cont = containerRef.current;
var triggerPoint =
cont.scrollWidth - cont.clientWidth - cont.scrollWidth * 0.2;
var scrollPos = cont.scrollLeft;
var triggerEl = document.getElementById('vr');
if (triggerEl) {
triggerEl.style.left = triggerPoint 'px';
}
if (scrollPos > triggerPoint && stateBool === false) {
console.log('triggered', nonStateBool, stateBool);
setStateBool(true);
}
};
containerRef.current.addEventListener('scroll', handler);
return () => containerRef.current.removeEventListener('scroll', handler);
}, [containerRef.current, stateBool]);
return (
<div>
<div ref={containerRef} id='container'>
<div className='divElement'>
inner element <vr id='vr'></vr>
</div>
</div>
<ul>
<li> nonStateBool {nonStateBool ? 'true' : 'false'}</li>
<li> State Bool {stateBool ? 'true' : 'false'}</li>
</ul>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
#container {
width: 200px;
background: grey;
padding: 1em;
overflow: hidden;
overflow-x: scroll;
}
.divElement {
width: 1500px;
height: 50px;
background: red;
}
.divElement vr {
border: 1px solid white;
position: relative;
height: 100%;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
But, a better approach would be to use the onScroll
prop for the container - best to only use vanilla DOM methods like addEventListener
when there no reasonable way to achieve the same results through React.
const App = (props) => {
var [stateBool, setStateBool] = React.useState(false);
var nonStateBool = false;
var containerRef = React.useRef(null);
const scrollHandler = (event) => {
var cont = containerRef.current;
var triggerPoint =
cont.scrollWidth - cont.clientWidth - cont.scrollWidth * 0.2;
var scrollPos = cont.scrollLeft;
var triggerEl = document.getElementById('vr');
if (triggerEl) {
triggerEl.style.left = triggerPoint 'px';
}
if (scrollPos > triggerPoint && stateBool === false) {
console.log('triggered', nonStateBool, stateBool);
setStateBool(true);
}
};
return (
<div>
<div ref={containerRef} onScroll={scrollHandler} id='container'>
<div className='divElement'>
inner element <vr id='vr'></vr>
</div>
</div>
<ul>
<li> nonStateBool {nonStateBool ? 'true' : 'false'}</li>
<li> State Bool {stateBool ? 'true' : 'false'}</li>
</ul>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
#container {
width: 200px;
background: grey;
padding: 1em;
overflow: hidden;
overflow-x: scroll;
}
.divElement {
width: 1500px;
height: 50px;
background: red;
}
.divElement vr {
border: 1px solid white;
position: relative;
height: 100%;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>