1

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;

0 Answers0