Skip to content
Advertisement

Multiple Buttons with their own Events in ReactJS

What would be the best way to handle multiple buttons that show/hide their respective DIVs.

Example of the Code I have below. As you can see it’s set to handle just the first button. I need a method to continually add more buttons that show/hide their own DIVs.

Example: When Button(A) is clicked DIV(A) fades in. If Button(A) is clicked again DIV(A) fades out. If/instead Button(B) is clicked and DIV(A) is visible, then DIV(A) fades out and DIV(B) fades in. so on.. etc..

const Example2: React.FC = () => {      
  const [style, setStyle] = useState("invis");

  const changeStyle = () => {
    console.log("you just clicked");

    setStyle("vis");
  };  

  return (        
    <Container className="content">                    
      <section className="section">                 
        <div className="video_section">
          <video src={videoUrl} width="" height ="" loop autoPlay muted> </video>
          <div className="video_words">
            <p><span>Video Words</span></p>
            <h1>
              <span>V</span>
              <span>I</span>
              <span>D</span>
              <span>E</span>
              <span>O</span>
            </h1> 
            <div className="TierTwo">
              <div className="Top1">
                <button type="button" onClick={changeStyle} className="Lrow1">
                  <img src="/img/image1.png" height="auto" width="auto" />                                 
                </button>
                <div className={style}>
                  <One />                                 
                </div>
                <div className={style}>
                  <Two />      
                </div>                                  
                <div className="Rrow1">
                  <button type="button" onClick={changeStyle} className="Rrow1">
                    <img src="/img/image2.png" height="auto" width="auto" />
                  </button>
                </div>
              </div>
              <div className="Top2">
                <button type="button" onClick={changeStyle} className="Lrow2">
                  <img src="/img/image3.png" height="auto" width="auto" />                                 
                </button>
                  <div className={style}>
                    <Three />                                 
                  </div>
                  <div className={style}>
                    <Four />      
                  </div>                                  
                  <div className="Rrow1">
                    <button type="button" onClick={changeStyle} className="Rrow2">
                      <img src="/img/image4.png" height="auto" width="auto" />
                    </button>
                  </div>
                </div>
                <div className="Top2">
                  ..etc..

Advertisement

Answer

As we want there only exists only one state that manage multiple buttons, such that no manual useState declaration, thus, we consider there is only a single state style, and it is an object, the key of style would be the button name or index, and by using changeStyle(index)() should trigger the onclick logic for button[index], for achieving the pattern like below:

(
  <button onClick={changeStyle(1)}>button1</button>
  <button onClick={changeStyle(2)}>button2</button>
  <button onClick={changeStyle(3)}>button3</button>

  <div>{style[1]}</div>
  <div>{style[2]}</div>
  <div>{style[3]}</div>
)

Since we want changeStyle(index) would by default set the state style[index] to "invis".

Hence will suppose the changeStyle function will be look

const changeStyle = (index) => {
  // set style[index] to "invis" by function setStyle

  // onclick function goes here
  return (event) => {
    // this block will be executed on every onclick
  }
}

And since we leveraged the setStyle function inside a IIFE to assign default value of each index, therefore, once we successfully set, we should design a mechanism that would stop constantly trigger it again, or else a forever render-loop will be happened.

const changeStyle = (index) => {
  if (!style[index]) {
    // setStyle goes here
  }
  // ...
}

And in order to improve atomicity of each setStyle operation, we should pass a callback function to the setStyle function instead of a value.

const changeStyle = (index) => {
  if (...) {
    setStyle((style) => {
      ...style,
      [index]: "invis",
    });
  }
  // ...
}

The handy function is done, now considering the onclick logic.

Regarding the logic of showing and hiding:

  1. apply all style to invis regardless of their current states
  2. toggle current style[index]: if it is “invis”, then set it to “vis”, vice versa.

hence, the setStyle function in onclick function should looks like:

setStyle((style) => ({
  // set all style to "invis"
  ...(
    Object.keys(style)
      .reduce((a, b) => ({
        ...a,
        [b]: "invis"
      }), {})
  ),
  // toggle current index state
  [index]: style[index] === "invis" ? "vis" : "invis",
}));

Complete solution:

const { useState } = React;

function App() {

  // the type of style will be { [key: any]: "invis" | "vis" }
  const [style, setStyle] = useState({});

  // IIFE for filling default value "invis"
  // return a callback when onclick happened
  const changeStyle = (index) => {
    // this will be run for the amount of button you have
    if (!style[index]) {
      setStyle((style) => ({
        ...style,
        [index]: "invis",
      }));
    }

    // for every onclick, this function will be run
    return () => {
      setStyle((style) => ({
        ...(
          Object.keys(style)
            .reduce((a, b) => ({
              ...a,
              [b]: "invis"
            }), {})
        ),
        [index]: style[index] === "invis" ? "vis" : "invis",
      }));
    };
  };

  return (
    <div className="App">
      <button onClick={changeStyle(1)}>button1</button>
      <button onClick={changeStyle(2)}>button2</button>
      <button onClick={changeStyle(3)}>button3</button>

      <div>{style[1]}</div>
      <div>{style[2]}</div>
      <div>{style[3]}</div>
    </div>
  );
}

ReactDOM.render(
    <App />,
    document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement