next.jsaccordionuse-ref

useRef not update


Can someone help me to understand why the contentHeight doesnt update? it seems that contentEl (useRef) is not updating correctly.

If u check the "LONG CONTENT" accordion item, u can see the content height is so much short and the content text remains hide. :(

Codesandbox preview: https://pb6coe.sse.codesandbox.io/

Codesandobx code: https://codesandbox.io/s/accordion-with-useref-pb6coe?file=/pages/index.js

I tried to console.log the contentEl for every click but the value is always the last accordion items -.-

Ty for helps


Solution

  • Your problem is you only set a single ref initially for content and useEffect with your setup run once solely after the component is mounted, so that's why it's always referred to the same content element.

    I'd suggest that you should use one ref for all content elements by indexes.

    Sandbox

    import { useState, useRef } from "react";
    import styled from "styled-components";
    import { project } from "../data";
    
    function IndexPage() {
      //initialize a list of content elements
      const contentEl = useRef([]);
      const [clicked, setClicked] = useState();
    
      const handleToggle = (index) => {
        if (clicked === index) {
          return setClicked("0");
        }
        setClicked(index);
      };
    
      return (
        <StyledAccordion>
          {project.map((prj, index) => {
            const { title, text, location, description, date } = prj;
    
            return (
              <StyledItem className={clicked === index ? "active" : ""} key={index}>
                <StyledButton onClick={() => handleToggle(index)}>
                  <StyledInfo>
                    <div className="title">{title}</div>
                    <div className="text">{text}</div>
                    <div>{location}</div>
                    <div>{date}</div>
                  </StyledInfo>
                  <span>{clicked === index ? "—" : "+"} </span>
                </StyledButton>
    
                <StyledContent
                  ref={(node) => (contentEl.current[index] = node)}
                  contentHeight={
                    contentEl.current[index]
                      ? contentEl.current[index].scrollHeight
                      : 0
                  }
                  active={clicked === index}
                >
                  <StyledDescription>{description}</StyledDescription>
                </StyledContent>
              </StyledItem>
            );
          })}
        </StyledAccordion>
      );
    }
    
    const StyledAccordion = styled.div`
      margin: 1em 0;
      list-style-type: none;
    `;
    
    const StyledItem = styled.li``;
    const StyledButton = styled.button`
      width: 100%;
      background: transparent;
      border: 2px solid #000;
      border-bottom: 0;
      display: flex;
      align-items: center;
      cursor: pointer;
    
      span {
        width: 100px;
        font-size: 3rem;
      }
    `;
    
    const StyledContent = styled.div`
      height: ${(props) => (props.active ? `${props.contentHeight}px` : "0px")};
      overflow: hidden;
      transition: all 0.3s ease-out;
    `;
    
    const StyledInfo = styled.div`
      display: flex;
      align-items: center;
      width: 90%;
      padding: 0.5em 2em;
      text-align: left;
      font-size: 1.2rem;
    
      div {
        width: 20%;
      }
    
      .title {
        width: 35%;
        font-weight: 600;
        font-family: "Saira Extra Condensed";
        font-size: 2rem;
      }
    
      .text {
        font-size: 1rem;
      }
    `;
    
    const StyledDescription = styled.div`
      background: rgb(255 134 215);
      font-size: 1.5rem;
      padding: 1.5em;
      border: 2px solid #000;
      border-bottom: 0;
    `;
    
    export default IndexPage;