'How to render loader on button clicked?

I pass an array of actions in child component. Then in child component I render three buttons. Each button get its own handler. Also I pass loader to child component.

I expect to get the following: on "Button 1" click the first button should become "Button 1 click" and no changes to other buttons.

What I actually get: on "Button 1" click. All buttons get "click" text.

How can I fix that? Codesandbox https://codesandbox.io/s/wispy-river-m2zgb?file=/src/App.tsx

interface IButtonBlockProps {
  actions: {
    tool: string;
    onClick: () => void;
  }[];
  loader: any;
}

enum Loader {
  Button1 = "button 1"
}

const ButtonBlock: React.FC<IButtonBlockProps> = ({ actions, loader }) => {
  return (
    <div>
      {actions.map((item, idx) => (
        <button key={idx} onClick={item.onClick}>
          {item.tool}

          {loader === Loader.Button1 && "Clicked"}
        </button>
      ))}
    </div>
  );
};

const App: React.FC = () => {
  const [loader, setLoader] = useState<Loader | null>(null);

  const handleClick = () => {
    console.log("on Button 1 click");
    setLoader(Loader.Button1);
  };

  const actions = [
    {
      tool: "Button 1",
      onClick: () => handleClick()
    },
    {
      tool: "Button 2",
      onClick: () => console.log("Button 2")
    },
    {
      tool: "Button 3",
      onClick: () => console.log("Button 3")
    }
  ];

  return (
    <div className="App">
      <ButtonBlock actions={actions} loader={loader} />
    </div>
  );
};


Solution 1:[1]

You can add one more check to your button if its Button1 or not something like below:

<button key={idx} onClick={item.onClick}>
  {item.tool}
  {loader === Loader.Button1 && item.tool === "Button 1" && "Clicked"}
</button>

Solution 2:[2]

What you could do instead is have an array of loading indexes, and whenever mapping the actions, if the loading array contains the index of the action, you can show clicked or loading or whatever.

This is assuming there would be something that after the button is no longer loading (like in the case of an API call completing) you would set the button to not loading by removing that index from the array.

Here is a modified version of your code sandbox that achieves this:

interface IButtonBlockProps {
  actions: {
    index: number;
    tool: string;
    onClick: () => void;
  }[];
  loader: any;
}

const ButtonBlock: React.FC<IButtonBlockProps> = ({ actions, loader }) => {
  return (
    <div>
      {actions.map((item, idx) => (
        <button key={idx} onClick={item.onClick}>
          {item.tool}
          {loader.includes(item.index) && `Clicked`}
        </button>
      ))}
    </div>
  );
};

const App: React.FC = () => {
  const [loadingButtons, setLoadingButtons] = useState<number[]>([]);
  console.log(loadingButtons);

  const handleClickButton1 = useCallback(() => {
    console.log("on Button 3 click");
    setLoadingButtons([...loadingButtons, 0]);
    setTimeout(() => {
      setLoadingButtons([...loadingButtons.filter((x) => x !== 0)]);
    }, 5000);
  }, [loadingButtons]);

  const handleClickButton2 = useCallback(() => {
    console.log("on Button 2 click");
    setLoadingButtons([...loadingButtons, 1]);
    setTimeout(() => {
      setLoadingButtons([...loadingButtons.filter((x) => x !== 1)]);
    }, 5000);
  }, [loadingButtons]);

  const handleClickButton3 = useCallback(() => {
    console.log("on Button 3 click");
    setLoadingButtons([...loadingButtons, 2]);
    setTimeout(() => {
      setLoadingButtons([...loadingButtons.filter((x) => x !== 2)]);
    }, 5000);
  }, [loadingButtons]);

  const actions = [
    {
      index: 0,
      tool: "Button 1",
      onClick: () => handleClickButton1()
    },
    {
      index: 1,
      tool: "Button 2",
      onClick: () => handleClickButton2()
    },
    {
      index: 2,
      tool: "Button 3",
      onClick: () => handleClickButton3()
    }
  ];

  return (
    <div className="App">
      <ButtonBlock actions={actions} loader={loadingButtons} />
    </div>
  );
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Neel Dsouza
Solution 2