cssreactjsuse-ref

Is it bad to use Refs to change styles in react?


here is a code example where i use Ref's to change style

import React, {useRef, useState, useEffect} from 'react'
import S from "./collapsible.module.css"

import PropTypes from 'prop-types'
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

function Collapsible(props) {
let myRef = useRef(null);
let buttonRef = useRef(null);
let [ button, setButton] = useState(true)


let Show = () => {
    if(button) {
setButton(false)
buttonRef.current.style.backgroundColor = "#555"
myRef.current.style.maxHeight = myRef.current.scrollHeight + "px";
} else {

setButton(true)
buttonRef.current.style.backgroundColor = "hsl(181, 100%, 11%)"
myRef.current.style.maxHeight =  "0px";
}
}


    return (
        <div
        className={S.body}
        >
            <button 
            className={S.collapsible}
onClick={Show}
ref={buttonRef}
            > {props.label}
            
            <div className={S.icon}> 
    {button? <FontAwesomeIcon icon={faPlus} />:
<FontAwesomeIcon icon={faMinus} />}
</div> 
            </button>
            <div 
            className={S.content}
            ref={myRef}
            >
<h3>{props.body}</h3>
            </div>
        </div>
    )
}
Collapsible.propTypes = {
    label: PropTypes.string,
    body: PropTypes.string,
  }
  
  Collapsible.defaultProps = {
    label: '',
    body: "",
  }
export default Collapsible

css:

.collapsible {
    display: flex;
    background-color: hsl(181, 100%, 11%);
    color: white;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    border: none;
    outline: none;
    font-size: 15px;
    border-radius: 3px;
    /* margin-bottom: 3px; */
    box-shadow: 0px 1px 5px 1px black;
    margin-top:13px;
  }
  .icon{
    color:white;
    position:absolute;
    right:50px;
text-align:right;
justify-content: flex-end;
}
  
  .active, .collapsible:hover {
    background-color: #555;
  }
  
  .content {
    padding: 0 18px;
    max-height: 0px;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
    background-color: #f1f1f1;
  }

This is just replicating this in React: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_animate

I have read that using Refs is bad, especially when using it to change the DOM, but if I didn't change the style with the exact amount shown in "scrollHeight" then the transition would be a messed up speed.

If there is another method Is this still bad practice?


Solution

  • It's more common practice to use a state value to determine the style like this:

      <button 
         className={S.collapsible} 
         onClick={Show} 
         style={{backgroundColor: button ? "#555" : "hsl(181, 100%, 11%)"}
       >
        {props.label}
        <div className={S.icon}>
          {button ? (
            <FontAwesomeIcon icon={faPlus} />
          ) : (
            <FontAwesomeIcon icon={faMinus} />
          )}
        </div>
      </button>
    
    

    Or have a conditional className for your styles:

     <button
        className={`${S.collapsible} ${
          button ? S.buttonColor : S.anotherButtonColor
        }`}
        onClick={Show}
      >
        {props.label}
        <div className={S.icon}>
          {button ? (
            <FontAwesomeIcon icon={faPlus} />
          ) : (
            <FontAwesomeIcon icon={faMinus} />
          )}
        </div>
      </button>
    
    

    and add .buttonColor and .anotherButtonColor to your CSS Module file (collapsible.module.css).

    .buttonColor {
        background-color: #555;
    }
    
    .anotherButtonColor {
        background-color: hsl(181, 100%, 11%);
    }
    
    

    For the maxHeight on myRef, I'd do something like:

      <div className={S.content} ref={myRef}>
        <div style={{ maxHeight: myRef.current.scrollHeight }}>
          <h3>{props.body}</h3>
        </div>
      </div>