Search code examples
javascriptreactjsref

React useRef from querySelectorAll


I want to make this simple task with Ref. First, you fill in how many buttons you want to generate, and then when you click on even I want to add class light to all buttons with even value and the same with odd value when clicking on odd. But this effect is simple with Vanilla JS just add querySelectorAll and after click add or remove class light, but in React I have a problem with understanding these refs case. When is only one ref I want how to do this but when I want to generate a few of them?

import React, { useState, useRef } from "react";
import "./style.css";

export default function App() {
  const [number, setNumber] = useState("");
  const evenButtonsRef = useRef([]);
  const oddButtonsRef = useRef([]);

  let buttons = [];
  const generateButtons = () => {
    for (let i = 1; i <= number; i++) {
      buttons.push(
        <button
          ref={i % 2 === 0 ? evenButtonsRef : oddButtonsRef}
          className={`${i % 2 === 0 ? "button even" : "button odd"}`}
        >
          {i}
        </button>
      );
    }
    return buttons;
  };

  const lightEvenButtons = () => evenButtonsRef.current.classList.add("light");

  const lightOddButtons = () => oddButtonsRef.current.classList.add("light");

  return (
    <div>
      <p>How many buttons?</p>
      <input
        type="text"
        onChange={e => setNumber(e.target.value)}
        placeholder="Number from 1 to 20"
      />
      {number && (
        <>
          <div className="button-group">
            <button onClick={lightEvenButtons}>Even</button>
            <button onClick={lightOddButtons}>Odd</button>
          </div>
          <div className="button-group">{generateButtons()}</div>
        </>
      )}
    </div>
  );
}

Link to code: https://stackblitz.com/edit/refbuttons?file=src/App.js


Solution

  • Actually you do not need to use refs here. The first reason is that it is much better to simply keep in state which buttons should be highlighted (odd or even) and set proper class depending on the state. The second reason is that you cannot put multiple elements into one ref, so you would need to have as many refs as buttons you have.

    Here is a code to implement desired functionality without refs at all:

    import React, { useState, useRef } from "react";
    import "./style.css";
    
    export default function App() {
      const [number, setNumber] = useState("");
      const [highlightType, setHighlightType] = useState("");
    
      const renderButtons = () => {
        const buttons = [];
        for (let i = 1; i <= number; i++) {
          let className = `${i % 2 === 0 ? "button even" : "button odd"}`;
          if (highlightType === "odd" && i % 2 !== 0) {
            className = `${className} light`;
          }
          if (highlightType === "even" && i % 2 === 0) {
            className = `${className} light`;
          }
          buttons.push(
            <button
              key={i}
              className={className}
            >
              {i}
            </button>
          );
        }
        return buttons;
      };
    
      return (
        <div>
          <p>How many buttons?</p>
          <input
            type="text"
            onChange={e => setNumber(e.target.value)}
            placeholder="Number from 1 to 20"
          />
          {number && (
            <>
              <div className="button-group">
                <button onClick={() => setHighlightType("even")}>Even</button>
                <button onClick={() => setHighlightType("odd")}>Odd</button>
              </div>
              <div className="button-group">{renderButtons()}</div>
            </>
          )}
        </div>
      );
    }
    

    Here is also a working demo. The renderButtons method may be a bit improved, but I think it clearly demonstrates the idea.