how to create reusable state in react hooks?

Viewed 818

I have 5 buttons in my app which I would like to change the background color based on button states, so now when I click one button it affects all the buttons, these buttons are used to copy something in UI, each one copies something different.

Note: I know I can create five states for each button and everything will be fine but I think this is a bad solution.

Also, assume each but have its own icon and text, we should be able onclick button to change the text to COPIED and change the icon of the button too

Like this here.

enter image description here

Here is what I have so far

    import React, { useState, useEffect, useRef } from 'react';
    
    function Mata() {
    const [isCopied, setIsCopied] = useState(0);
    
    
        return (
            <div className="container">
                <button style={{ backgroundColor: isCopied ? '#262626' : '#F3F3F3'}} className={`btn1 ${isCopied && activeTab}`} onClick={handleBtn1}>Copy anything</button>
                <button style={{ backgroundColor: isCopied ? '#262626' : '#F3F3F3'}} className={`btn2 ${isCopied && activeTab}`} onClick={handleBtn2}>Copy something</button>
                <button style={{ backgroundColor: isCopied ? '#262626' : '#F3F3F3'}} className={`btn3 ${isCopied && activeTab}`} onClick={handleBtn3}>Copy Imgae</button>
                <button style={{ backgroundColor: isCopied ? '#262626' : '#F3F3F3'}} className={`btn4 ${isCopied && activeTab}`} onClick={handleBtn4}>Copy text</button>
                <button style={{ backgroundColor: isCopied ? '#262626' : '#F3F3F3'}} className={`btn5 ${isCopied && activeTab}`} onClick={handleBtn5}>Copy Link</button>
             
            </div>
        )
    }
    
    export default Mata

How can I create just one state eg const[isCopied, setIsCopied] = useState(); and use it in any button related to copying something in UI?

3 Answers

Perhaps create your own React Component which encapsulates your idea, and use that component.

Here is a codesandbox showing how it can be customized to fit your needs, as well as passing a function for the onClick event for each button.

Below a simple snippet.

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

export default function App() {
  return (
    <div className="App">
      <ColouredButton title="Copy something" />
      <ColouredButton title="Copy something" />
      <ColouredButton title="Copy Imgae" />
      <ColouredButton title="Copy text" />
      <ColouredButton title="Copy Link" />
    </div>
  );
}

const ColouredButton = props => {
  const [isCopied, setIsCopied] = useState(0);
  return (
    <button
      style={{ backgroundColor: isCopied ? "#262626" : "#F3F3F3" }}
      className={`btn1 ${isCopied && activeTab}`}
      onClick={() => setIsCopied(prevState => !prevState)}
    >
      {props.title}
    </button>
  );
};

Probably three separate answers here:

I'd just use file useState calls

Since you've said the handlers are all separate, and you're writing the buttons separately, I'd just use five useState calls. Simple, clear, avoids cross-talk between the buttons.

But you've said you don't want to do that, so on to answer #2... :-)

Use an array of handlers and an object for the flags

(Actually, I don't like this one as much as the following one; so read it, but then keep reading.)

You could put those functions in an array and probably make the flags an object keyed by the id of the button, which we can create from the index:

function Mata() {
    const [isCopied, setIsCopied] = useState({});

    // ...create handlers...

    const buttonHandlers = [handleBtn1, handleBtn2, handleBtn3, handleBtn4, handleBtn5];

    return (
        <div className="container">
            {buttonHandlers.map((handleBtn, index) => {
                const flag = isCopied[index];
                return <button id={index} style={{ backgroundColor: flag ? '#262626' : '#F3F3F3'}} className={`btn${index+1} ${flag && activeTab}`} onClick={handleBtn}>Copy anything</button>;
            })}
        </div>
    );
}

Within the handler function:

function handleBtn1(evt) {
    // ...logic...
    if (/*...need to set the flag...*/) {
        setIsCopied(isCopied => ({...isCopied, [evt.target.id]: true}));
    } else if (/*...need to clear the flag...*/) {
        setIsCopied(isCopied => ({...isCopied, [evt.target.id]: true}));
    }
}

Someone may tell you that id values can't start with digits. That's incorrect, they can. It's CSS ID selectors that can't start with an unescaped digit, but we're not using CSS here.

Use an object for the flags keyed by the id of the button

Or do away with the array entirely as it's not buying you much anymore:

function Mata() {
    const [isCopied, setIsCopied] = useState({});

    // ...create handlers...

    return (
        <div className="container">
            <button id="btn1" style={{ backgroundColor: isCopied.btn1 ? '#262626' : '#F3F3F3'}} className={`btn1 ${isCopied.btn1 && activeTab}`} onClick={handleBtn1}>Copy anything</button>
            <button id="btn2" style={{ backgroundColor: isCopied.btn2 ? '#262626' : '#F3F3F3'}} className={`btn2 ${isCopied.btn2 && activeTab}`} onClick={handleBtn2}>Copy something</button>
            <button id="btn3" style={{ backgroundColor: isCopied.btn3 ? '#262626' : '#F3F3F3'}} className={`btn3 ${isCopied.btn3 && activeTab}`} onClick={handleBtn3}>Copy Imgae</button>
            <button id="btn4" style={{ backgroundColor: isCopied.btn4 ? '#262626' : '#F3F3F3'}} className={`btn4 ${isCopied.btn4 && activeTab}`} onClick={handleBtn4}>Copy text</button>
            <button id="btn5" style={{ backgroundColor: isCopied.btn5 ? '#262626' : '#F3F3F3'}} className={`btn5 ${isCopied.btn5 && activeTab}`} onClick={handleBtn5}>Copy Link</button>
        </div>
    );
}
Related