Search code examples
javascriptcssreactjsreact-forwardref

How to check if a div is overflowing in react functional component


I am trying to find out if a div has overflown text and show show more link if it does. I found this stackoverflow answer to check if a div is overflowing. According to this answer, I need to implement a function which can access styles of the element in question and do some checks to see if it is overflowing. How can I access the styles of an element. I tried 2 ways

1. Using ref

import React from "react";
import "./styles.css";

export default function App(props) {
  const [showMore, setShowMore] = React.useState(false);
  const onClick = () => {
    setShowMore(!showMore);
  };

  const checkOverflow = () => {
    const el = ref.current;
    const curOverflow = el.style.overflow;

    if ( !curOverflow || curOverflow === "visible" )
        el.style.overflow = "hidden";

    const isOverflowing = el.clientWidth < el.scrollWidth 
        || el.clientHeight < el.scrollHeight;

    el.style.overflow = curOverflow;

    return isOverflowing;
  };

  const ref = React.createRef();

  return (
    <>
      <div ref={ref} className={showMore ? "container-nowrap" : "container"}>
        {props.text}
      </div>
      {(checkOverflow()) && <span className="link" onClick={onClick}>
        {showMore ? "show less" : "show more"}
      </span>}
    </>
  )
}

2. Using forward ref

Child component

export const App = React.forwardRef((props, ref) => {
  const [showMore, setShowMore] = React.useState(false);
  const onClick = () => {
    setShowMore(!showMore);
  };

  const checkOverflow = () => {
    const el = ref.current;
    const curOverflow = el.style.overflow;

    if (!curOverflow || curOverflow === "visible") el.style.overflow = "hidden";

    const isOverflowing =
      el.clientWidth < el.scrollWidth || el.clientHeight < el.scrollHeight;

    el.style.overflow = curOverflow;

    return isOverflowing;
  };

  return (
    <>
      <div ref={ref} className={showMore ? "container-nowrap" : "container"}>
        {props.text}
      </div>
      {checkOverflow() && (
        <span className="link" onClick={onClick}>
          {showMore ? "show less" : "show more"}
        </span>
      )}
    </>
  );
});

Parent component

import React from "react";
import ReactDOM from "react-dom";

import { App } from "./App";

const rootElement = document.getElementById("root");
const ref = React.createRef();
ReactDOM.render(
  <React.StrictMode>
    <App
      ref={ref}
      text="Start editing to see some magic happen! Click show more to expand and show less to collapse the text"
    />
  </React.StrictMode>,
  rootElement
);

But I got the following error in both approaches - Cannot read property 'style' of null. What am I doing wrong? How can I achieve what I want?


Solution

  • As Jamie Dixon suggested in the comment, I used useLayoutEffect hook to set showLink true. Here is the code

    Component

    import React from "react";
    import "./styles.css";
    
    export default function App(props) {
      const ref = React.createRef();
      const [showMore, setShowMore] = React.useState(false);
      const [showLink, setShowLink] = React.useState(false);
    
      React.useLayoutEffect(() => {
        if (ref.current.clientWidth < ref.current.scrollWidth) {
          setShowLink(true);
        }
      }, [ref]);
    
      const onClickMore = () => {
        setShowMore(!showMore);
      };
    
      return (
        <div>
          <div ref={ref} className={showMore ? "" : "container"}>
            {props.text}
          </div>
          {showLink && (
            <span className="link more" onClick={onClickMore}>
              {showMore ? "show less" : "show more"}
            </span>
          )}
        </div>
      );
    }
    
    

    CSS

    .container {
      overflow-x: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      width: 200px;
    }
    
    .link {
      text-decoration: underline;
      cursor: pointer;
      color: #0d6aa8;
    }