5

I have a problem getting clientWidth from useEffect.

There is a lazyLoad which loads a page with users.

import {Suspense, lazy} from 'react';

const UsersContainer = lazy (() => import ('./ users / users-container'));

const Index = (props) => {
    return (
        <Suspense fallback = {<div id = {'loading'} />}>
            <UsersContainer />
        </Suspense>
    )
}

export default Index;

UsersContainer has a child component dataTooltip, which displays the full User name if it does not fit into the block.

import React, {useEffect, useRef} from 'react';

import '../common.scss';

const DataTooltip = ({title, ... props}) => {
    let ref = useRef ();

    useEffect (() => {
        if (ref.current.scrollWidth> ref.current.clientWidth) {
            ref.current.setAttribute ('data-tooltip', title)
        }
    });

    return (
        <div ref = {ref} className = {'tooltip'}>
            {
                React.cloneElement (props.children, {ref: ref})
            }
        </div>
    )
}

export default DataTooltip;

What's the problem?

After the UsersContainer is loaded and rendered in the DOM, it has 'display: none' and at the same moment useEffect in DataTooltip is triggered asynchronously.

As a result, DataTooltip says that clientWidth = 0, due to the fact that the parent has 'display: none'.

How to make useEffect work after lazyLoad removed 'display: none'.

PS: useLayoutEffect works the same, clientWidth = 0


Solved the problem this way:

<Suspense fallback={<div id={'loading'}/>}>
   <Main/>
   <Acquaintance/>
   <UsersContainer/>
   <RegisterContainer/>
</Suspense>

to

<Suspense fallback={<div id={'loading'}/>}>
    <Main/>
</Suspense> 
            
<Suspense fallback={<div id={'loading'}/>}>
    <Acquaintance/> 
</Suspense>
            
<Suspense fallback={<div id={'loading'}/>}>
    <UsersContainer/>
</Suspense>
            
<Suspense fallback={<div id={'loading'}/>}>
    <RegisterContainer/>
</Suspense>
TilikWebDeloper
  • 219
  • 3
  • 7

1 Answers1

0

I don't know if this solves your issue - but one thing I notice immediately is that you're missing the dependency array from your useEffect hook.

import React, {useEffect, useRef} from 'react';
...

const DataTooltip = ({title, ... props}) => {
    let ref = useRef ();

    useEffect (() => {
        if (ref.current.scrollWidth> ref.current.clientWidth) {
            ref.current.setAttribute ('data-tooltip', title)
        }
    });

    return (...)
}

export default DataTooltip;

should be:

import React, {useEffect, useRef} from 'react';
...

const DataTooltip = ({title, ... props}) => {
    let ref = useRef ();

    useEffect (() => {
        if (ref.current.scrollWidth> ref.current.clientWidth) {
            ref.current.setAttribute ('data-tooltip', title)
        }
    }, [ref]);

    return (...)
}

export default DataTooltip;

Also keep in mind that this will cause the component to re-render whenever ref changes, per the documentation of the useEffect hook you should always declare any variables from the upper scope used in the useEffect hook as part of the dependency array, and if you dont dont use any such variables you should pass an empty array still to prevent running an infinite loop.

  • Did not help. I load the page in Slow 3G mode. Lazy Load loaded users but set 'display: none! Important' and at this time Users got useEffect which could not get clientWidth due to 'display: none! Important'. Then Lazy Load removes 'display: none'. – TilikWebDeloper Feb 28 '21 at 13:03
  • Yeah I didnt expect it would solve the actual issue - it was just AN issue I noticed that would've caused a problem with your code regardless of whether the original issue is solved or not – Aasmund Berge Endresen Feb 28 '21 at 13:05
  • 1. LazyLoad rendered the page with 'display: none'; 2. The page triggered useEffect; 3. LazyLoad removed 'display: none'. – TilikWebDeloper Feb 28 '21 at 13:05
  • But why are you doing this in the first place? The purpose of lazyloading content and using Suspense is that it wont render the content before the component is ready anyways. It seems you are packaging your own lazyload within the React.Lazy/Suspense solution. – Aasmund Berge Endresen Feb 28 '21 at 13:09
  • If you absolutely must do it this way - I would setting a local state variable to check the styles of your component before your `useEffect`-hook, and then inject this variable as a dependency for `useEffect`, so when it is updated it will trigger `useEffect` to run again. Any variables you put in the dependency array will cause the function to run again if they are changed. – Aasmund Berge Endresen Feb 28 '21 at 13:12