I am trying to access the horizontal scroll bar with buttons in react but turns out it is not working because of error that cannot read the null values of .scrollLeft.
<>
<div className="containerOuterSider">
<FaAngleLeft className= "FaAngleLeft" onClick={slide('left')}/>
<div id="container3" className="container3">
{products &&
products
.map((product) => (
<AAsOfLowNav key={product._id} product={product} />
))
.reverse()}
</div>
<FaAngleRight className="FaAngleRight" onClick={slide('right')}/>
</div>
</>
and the main function is
function slide(direction) {
var container = document.getElementById('container3');
let scrollCompleted = 0;
var slideVar = setInterval(function() {
if (direction === 'left') {
container.scrollLeft -= 10;
} else {
container.scrollLeft += 10;
}
scrollCompleted += 10;
if (scrollCompleted >= 100) {
window.clearInterval(slideVar);
}
}, 50);
}
The error I am facing is:
Uncaught TypeError: Cannot read properties of null (reading 'scrollLeft') at LowerCatNav.js:31:1 Uncaught TypeError: Cannot read properties of null (reading 'scrollLeft') at LowerCatNav.js:33:1
How can I make it right? Should I use the hooks or anything else?
You are immediately invoking the slide function while rendering:
<FaAngleLeft
className="FaAngleLeft"
onClick={slide('left')} // <-- immediately invoked when rendered
/>
...
<FaAngleRight
className="FaAngleRight"
onClick={slide('right')} // <-- immediately invoked when rendered
/>
The React component hasn't been fully rendered and pushed to the DOM, so queries to the DOM, i.e. document.getElementById('container3'), return null.
Fix the click handler so slide is not being immediately invoked.
<FaAngleLeft
className="FaAngleLeft"
onClick={() => slide('left')} // <-- asynchronously invoked when clicked
/>
...
<FaAngleRight
className="FaAngleRight"
onClick={() => slide('right')} // <-- asynchronously invoked when clicked
/>
It is considered a React anti-pattern to directly query the DOM for DOMNodes, use a React ref for this.
Example:
...
const containerRef = React.useRef(); // <-- (1) create Ref
const sliderTimerRef = React.useRef();
useEffect(() => {
return () => {
// clear any running intervals on component unmount
clearInterval(sliderTimerRef.current);
};
}, []);
...
function slide(direction) {
// clear any previously set intervals and reset scrollCompleted
clearInterval(sliderTimerRef.current);
let scrollCompleted = 0;
sliderTimerRef.current = setInterval(function() {
const container = containerRef.current; // <-- (3) access current ref value
if (direction === 'left') {
container?.scrollLeft -= 10; // <-- (4) Optional Chaining null check
} else {
container?.scrollLeft += 10; // <-- (4) Optional Chaining null check
}
scrollCompleted += 10;
if (scrollCompleted >= 100) {
clearInterval(sliderTimerRef.current);
}
}, 50);
}
...
return (
<>
<div className="containerOuterSider">
<FaAngleLeft className="FaAngleLeft" onClick={() => slide('left')}/>
<div
ref={containerRef} // <-- (2) attach ref to element
id="container3"
className="container3"
>
{products
.map((product) => (
<AAsOfLowNav key={product._id} product={product} />
))
.reverse()
}
</div>
<FaAngleRight className="FaAngleRight" onClick={() => slide('right')}/>
</div>
</>
);
slide is called when #container3 is not yet created.
Change onClick={slide(...)} to onClick={() => slide(...)}
Access to global elements (e.g. by id) should be avoided.
Prefer ref/createRef/useRef to access elements belonging to your component.