• Skip to main content
  • Skip to primary sidebar
  • Skip to footer
  • About
  • Life
  • Tech
  • Travel
  • Work
  • Questions
  • Contact

Welcome

.

A better way to write this React Class Component with Hooks?

April 10, 2020 by

Questions › A better way to write this React Class Component with Hooks?
0
Vote Up
Vote Down
Garmaine asked 3 years ago

I have a section with a fixed height. I don't know when the component mounts (first renders) whether the content coming in will fit or not. If it does NOT fit, then I need to render a 'Read More' button.

It looks like this: enter image description here

I wrote this originally as a Class component using the lifecycle methods DidMount/DidUpdate:

Class Component

import React, { createRef } from "react"
import styled from "@emotion/styled"

import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"

const StyledHeightContainer = styled.div`
  max-height: 150px;
  overflow: hidden;
`

class ParagraphList extends React.Component {
  state = {
    overflowActive: false,
  }
  wrapper = createRef() // so we can get a ref to the height container

  isOverflowing(el) {
    if (el) return el.offsetHeight < el.scrollHeight
  }

  componentDidMount() {
    this.setState({ overflowActive: this.isOverflowing(this.wrapper.current) })
  }

  componentDidUpdate() {
    if (this.wrapper.current && !this.state.overflowActive) {
      this.setState({
        overflowActive: this.isOverflowing(this.wrapper.current),
      })
    }
  }

  handleClick() {
    this.setState({ overflowActive: false })
  }

  render() {
    const { moreButtonText, titleText, paragraphs, theme } = this.props

    return (
      <>
        <Section overflowActive={this.state.overflowActive}>
          {this.state.overflowActive || !this.wrapper.current ? (
            <StyledHeightContainer ref={this.wrapper}>
              <Paragraphs paragraphs={paragraphs} />
            </StyledHeightContainer>
          ) : (
            <Paragraphs paragraphs={paragraphs} />
          )}
        </Section>
        {overflowActive ?
         <ButtonReadMore
           onClicked={handleClick.bind(this)}
           moreButtonText={moreButtonText}
           theme={theme}
         />
        : null}
      </>
    )
  }
}

export default ParagraphList

My best way to explain the flow:

  1. When the component mounts, the flag is false and we have no reference to the div so the StyledHeightContainer will try to render and thus provide a ref to it

  2. In componentDidMount -> try to set the overflow flag (which will be false because at this point we do not yet have rendering completed so the ref will be null). But by setting the flag anyway, we queue an additional render pass

  3. 1st INITIAL rendering completes -> we have a ref to the div now

  4. The 2nd (queued) render occurs, firing the componentDidUpdate -> we can calculate the overflow and set the flag to true when the content overflows

  5. When the user clicks the button -> set the flag to false, which will trigger a re-render and hence the StyledHeightContainer will be removed from the DOM.

Functional Component With Hooks

Sandbox of the code

When I re-wrote this as a functional component using Hooks, I ended up with this:

import React, { createRef, useEffect, useState } from "react"
import styled from "@emotion/styled"

import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"

const StyledHeightContainer = styled.div`
  max-height: 150px;
  overflow: hidden;
`

const ParagraphList = ({ moreButtonText, titleText, paragraphs, theme }) => {
  const [overflowActive, setOverflowActive] = useState(false)
  const [userClicked, setUserClicked] = useState(false)
  const wrapper = createRef(false) // so we can get a ref to the height container

  const isOverflowing = el => {
    if (el) return el.offsetHeight < el.scrollHeight
  }

  useEffect(() => {
    if (!userClicked && !overflowActive && wrapper.current) {
      setOverflowActive(isOverflowing(wrapper.current))
    }
  }, [userClicked]) // note: we only care about state change if user clicks 'Read More' button

  const handleClick = () => {
    setOverflowActive(false)
    setUserClicked(true)
  }

  return (
    <>
      <Section theme={theme} overflowActive={overflowActive}>
        {!userClicked && (overflowActive || !wrapper.current)  ? (
          <StyledHeightContainer ref={wrapper}>
            <Paragraphs paragraphs={paragraphs} />
          </StyledHeightContainer>
        ) : (
          <Paragraphs paragraphs={paragraphs} />
        )}
      </Section>
      {overflowActive ?
        <ButtonReadMore
          onClicked={handleClick.bind(null)}
          moreButtonText={moreButtonText}
          theme={theme}
        />
        : null}
    </>
  )
}

export default ParagraphList

I was surprised that I needed to add another state (userClicked), which is how I force the 2nd render to occur (ie. the equivalent to the componentDidUpdate in the class solution).

Is this correct or can someone see a more concise way to write the 2nd solution?

NOTE

One of the reasons I ask is because in the console I get this warning:

48:6  warning  React Hook useEffect has missing dependencies:
'overflowActive' and 'wrapper'. Either include them or remove the
dependency array  react-hooks/exhaustive-deps

and I don't THINK I want to add them to the dependency array, as I don't want to trigger rendering when they change…?

Are you looking for the answer?
Original Question and Possible Answers can be found on `http://stackoverflow.com`

Question Tags: javascript, react-hooks, reactjs

Please login or Register to submit your answer




Primary Sidebar

Tags

Advancements architecture beautiful life best building calling city commercial convenience employment Finances Cognitive decline Future gadgets Hidden Gems highway Home houses hydration Impact Innovations lamp lighting Mental health military tech Must-See New York City occupation Productivity recreation romance sepia shopping sippy cups smartphones social Technological breakthroughs technology toddlers Treasures turns Uncover Well-being Wonders Work Young onset dementia

Newsletter

Complete the form below, and we'll send you all the latest news.

Footer

Footer Funnies

Who knew that reading the footer could be such a hilarious adventure? As we navigate websites, books, and documents, we often stumble upon the unassuming space at the bottom, only to discover a treasure trove of amusement. In this side-splitting compilation, we present 100 jokes that celebrate the unsung hero of content – the footer. Get ready to chuckle, giggle, and maybe even snort as we dive into the world of footnotes, disclaimers, and hidden comedic gems. Brace yourself for a wild ride through the footer!

Recent

  • Unveiling the Enigma: Almost-Magical Lamp Lights Highway Turns
  • The Impact of Young Onset Dementia on Employment and Finances: Optimizing Post-Diagnostic Approaches
  • 11 Wonders of 2023 Technological Breakthrough – Unveiling the Future
  • Work from Home and Stay Mentally Sane – Achieve Productivity and Well-being
  • Hidden Gems of New York City – Uncover the Must-See Treasures!

Search

Tags

Advancements architecture beautiful life best building calling city commercial convenience employment Finances Cognitive decline Future gadgets Hidden Gems highway Home houses hydration Impact Innovations lamp lighting Mental health military tech Must-See New York City occupation Productivity recreation romance sepia shopping sippy cups smartphones social Technological breakthroughs technology toddlers Treasures turns Uncover Well-being Wonders Work Young onset dementia

Copyright © 2023