useEffect is executed before content has fully loaded

I have a view with content that I get with searchContent. I map the received items in the return, and then I want to call a function in useEffect. The problem I have is that the function sets the height of an element to the height of its children, but the content of the children isn't loaded completely before the useEffect is executed. Because of this, the height is too small, and the child elements won't display correctly. As long as I have static content in the return, everything works properly. I thought that useEffect was getting called after the content had loaded, or am I wrong?
The problem occurs when I reload the page, as soon as I click on a button to filter the items, the height is correct.

This is how the elements look.

This is how it should look like.

This is the useEffect function
image

The view

import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { searchContent } from '@plone/volto/actions';
import PropTypes from 'prop-types';
import { Helmet } from '../../../../../omelette/src/helpers/Helmet/Helmet';
import 'remixicon/fonts/remixicon.css'


const getPeopleContent = (array = []) =>
  array.map((obj, item) => {
    obj[0] = item;
    return obj;
  }, {}
);

const IsotopeView = ({ data }) => {
    const dispatch = useDispatch();
    const people = useSelector((state) =>
        getPeopleContent(state.search.subrequests.person?.items),
    );

    const content = useSelector((state) => state.workflow.transition);

    useEffect(() => {
        dispatch(
          searchContent(
            '/',
            {
              portal_type: ['person'],
              review_state: 'private',  //Has to be changed to public when deploying
              fullobjects: true,
            },
            'person',
          ),
        );
      }, [dispatch, content]);

    // init one ref to store the future isotope object
    var isotope;

    if (typeof window !== "undefined") {
        isotope = window.Isotope;
    }

    const handleClick = event => {
        const buttons = document.getElementsByClassName("is-checked");
        console.log(buttons)
        Array.prototype.forEach.call(buttons, function(button) {
            button.classList.remove('is-checked');
        })
        event.currentTarget.classList.toggle('is-checked');
    };


    // store the filter keyword in a state  
    const [filterKey, setFilterKey] = useState('*')
    const handleFilterKeyChange = key => (event) => {setFilterKey(key); handleClick(event);}

    // initialize an Isotope object with configs
    useEffect(() => {
        isotope.current = new Isotope('.grid', {
            itemSelector: '.grid-item',
            sortBy: 'random',
        })

        handleFilterKeyChange('*')
        // cleanup
        return () => isotope.current.destroy()
    })

    // handling filter key change
    useEffect(() => {
        filterKey === '*'
          ? isotope.current.arrange({filter: `*`})
          : isotope.current.arrange({filter: `.${filterKey}`})
    }, [filterKey])
  
    return(
        <>
            <Helmet>
                <script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
            </Helmet>
            <div id="filters" className="button-group filter-button-group">
                <button className='filterBtn is-checked' onClick={handleFilterKeyChange('*')}>Alle</button>
                {data.default_tags.map((filter) => (
                    <button className='filterBtn' onClick={handleFilterKeyChange(filter)}>{ filter }</button>
                ))}
            </div>

            <hr />

            <div className='grid'>
                {people.map((person) => (
                    <div className={'grid-item ' + person.tags_field.map((tag) => (tag.token))} key={person.UID}>
                        <img className='gridImg' src={person.img_field.download} alt={person.img_field.filename} />
                        <div className='girdContent'>
                            <p className='gridContentName'>{ person.name_field }</p>
                            <p>{ person.position_field }</p>
                            <p>{ person.entrance_field }</p>
                            <a href={'mailto:' + person.email_field}><i className="ri-mail-line"></i></a>
                            {person.tags_field.map((tag, i) => (
                                <button className='gridContentTag' key={person.UID + i}>{ tag.token }</button>
                            ))} 
                        </div>
                    </div>
                ))}
            </div>
        </>
   );
}; 

IsotopeView.propTypes = {
    data: PropTypes.objectOf(PropTypes.any).isRequired,
};

export default IsotopeView;

You can include a dependency array in the useEffect where you construct the Isotope, and only construct it if the people.length > 1

@tiberiuichim I'm not sure how to do this. Does the dependency array needs to be empty?

Like this?

    useEffect(() => {
        if(people.length > 1) {
            isotope.current = new Isotope('.grid', {
                itemSelector: '.grid-item',
                sortBy: 'random',
            })
            // cleanup
            return () => isotope.current.destroy()
        }
    }, [])

add people to the dependency array

@tiberiuichim This fixes the overlapping problem, thanks. But now I have the problem that if you reload the page, the height of the outer div (the one with class "grid") is set wrong because the people elements aren't fully loaded and isotope sets the height of that div to the height of the people boxes.

you need to rebuild the isotope when the people array changes.

@tiberiuichim

I don't think the people array changes :thinking:. Isotope is not removing any items from the array it just sets the elements to "display: none;". Or is something else causing the people array to change? And how could i trigger the rebuild?

Maybe it's easier if you refactor your code in two separate concerns, one parent component that fetches the data and pass it down to the child component, that displays the data. Then the parent component can make sure that it renders the child component only when it has all the data. Then the child, which will instantiate the isotope, will be sure that everything is "already there".

@tiberiuichim Okey i will try this. Thank you very much. For the moment my solution was to set a min-height to the div, but that's not the propper way.