I am working on a bar website for a friend and I have a problem: Right after the hero section of this menu page, there is a category-selector, which should have navigational buttons to the left and right.
Problem: Sometimes these don't get rendered, sometimes they do. It's really hard to reproduce but ctrl+f5 seems to be most stable to do the job.
I use intersection observers to determine if the first or last button of the category selector component are visible or not and then act accordingly.
My suspicion is, that I am doing something wrong regarding the timing of my .observe() method. But I could be wrong. Console is giving no hint as to what happens.
In my component there is a method to create the observer:
createObserver(target) {
const options = {
root: document.body,
rootMargin: '0px',
threshold: 0.5,
};
return new IntersectionObserver((entries) => {
entries.forEach(entry => {
target.classList.toggle('cselector-navButton-show', !entry.isIntersecting)
})
}, options);
}
I call this method in the componentDidUpdate()
lifycycle method. First I get the first and last button, then I use createObserver(target)
to create an observer. Then I call .observe on both buttons.
componentDidUpdate() {
/* Get DOM Element we want to observe */
const buttons = Array.from(document.querySelectorAll('.cselector-scrollBox button'));
/* Get Nav-Buttons whose visibility we want to toggle */
const nextButton = document.getElementById("nextbutton");
const prevButton = document.getElementById("prevbutton");
/* Create Intersection Observers */
const firstObserver = this.createObserver(prevButton);
const lastObserver = this.createObserver(nextButton);
/* Wrap in window.onload to make sure page has finished loading before setting observers */
window.onload = function() {
firstObserver.observe(buttons[0]);
lastObserver.observe(buttons[buttons.length - 1]);
}
}
I found the hint somewhere on SO (i think) to wrap the .observe in a window.onload to make sure the component is there before trying to observe it. Without this, it fails miserably, as the element i try to observe seems to not be rendered yet: Uncaught TypeError: IntersectionObserver.observe: Argument 1 is not an object.
Same thing happens when i try to move it to ComponenDidMount()
The weird thing is that I get no error on the console, so I am not even sure the dom-element being observed not being present is the actual problem.
So yeah, some help from someone being more proliferate in react class based syntax would be much appreciated here :)
Here is the entire component:
import React from 'react';
import SelectorButton from './SelectorButton';
class CategorySelector extends React.Component {
constructor(props){
super(props);
this.scrollTo = this.scrollTo.bind(this)
this.createObserver = this.createObserver.bind(this)
this.state = {
scrollbox: null
}
}
componentDidMount() {
/* When Component is mounted, add scrollbox to state, to be used later */
this.setState({scrollbox: document.querySelector(".cselector-scrollBox")});
}
componentDidUpdate() {
/* Get DOM Element we want to observe */
const buttons = Array.from(document.querySelectorAll('.cselector-scrollBox button'));
/* Get Nav-Buttons whose visibility we want to toggle */
const nextButton = document.getElementById("nextbutton");
const prevButton = document.getElementById("prevbutton");
/* Create Intersection Observers */
const firstObserver = this.createObserver(prevButton);
const lastObserver = this.createObserver(nextButton);
/* Wrap in window.onload to make sure page has finished loading before setting observers */
window.onload = function() {
firstObserver.observe(buttons[0]);
lastObserver.observe(buttons[buttons.length - 1]);
}
}
/* Scrolls to position in slider by passing an offset. Snap-Slider-Css does heavy lifting on alignment */
scrollTo(offset) {
const scrollbox = this.state.scrollbox;
scrollbox.scrollBy({
left: offset,
behavior: "smooth"
})
}
/* Returns an Intersection Observer, that toggles the visibility of arrow-nav-buttons */
createObserver(target) {
const options = {
root: document.body,
rootMargin: '0px',
threshold: 0.5,
};
return new IntersectionObserver((entries) => {
entries.forEach(entry => {
target.classList.toggle('cselector-navButton-show', !entry.isIntersecting)
})
}, options);
}
render() {
return (
<>
<div className="d-flex align-items-center">
<div>
{ this.props.selectedCategory !== null &&
<button className="me-3 btn btn-primary btn-s d-inline deselect-button"
onClick={this.props.deselectAll}>
<span className="col-primary"><i className="fa-solid fa-xmark"></i></span>
</button>}
</div>
<div className='cselector-scrollWrapper'>
<button id="prevbutton" className="cselector-navButton prev"
onClick={() => this.scrollTo(-200)}>
<span className="col-primary"><i className="fa-solid fa-chevron-left"></i></span>
</button>
<div className="cselector-scrollBox py-2">
{
Array.from(this.props.categories).map(category => {
return(
<SelectorButton
category={category}
selectCategory={this.props.selectCategory}
selectedCategory={this.props.selectedCategory}
key={`cat-${category.id}`}
/>
)
})
}
</div>
<button id="nextbutton" className="cselector-navButton next"
onClick={() => this.scrollTo(200)}>
<span className="col-primary"><i className="fa-solid fa-chevron-right"></i></span>
</button>
</div>
</div>
</>
)
}
}
export default CategorySelector;