How to have component reusable in React TypeScript when passing array of objects as data

Issue

I want to make a buttonGroup that can be reused as a React component in React/Typescript. I know I can pass objects as props so that they can be mapped over. However currently my data objects take in functions that update the state of the active class of the buttons, I am a little confused as to how to do this? So I want to be able to do <ButtonGroup data={objects} /> but still maintain the active states of the buttons.

EDIT: Also as it is a button group, would need to have individual functions for each button too.

Any Idea’s? code so far:

component:

import React from 'react';
import {ButtonGroupState} from '../../shared/interfaces/ButtonGroup.interface';

const ButtonGroup: React.FunctionComponent = () => {

    const objectsArray = [{
        id: 1,
        title: '1 element',
        function(index: number) {
            setActive({ ...active, activeObject: active.objects[index] })
        }

    }, {
        id: 2,
        title: '2 element',
        function(index: number) {
            setActive({ ...active, activeObject: active.objects[index] })

        }
    }, {
        id: 3,
        title: '3 element',
        function(index: number) {
            setActive({ ...active, activeObject: active.objects[index] })

        }
    }]

    const [active, setActive] = React.useState<ButtonGroupState>({
        activeObject: objectsArray[0],
        objects: objectsArray
    })

    const toggleActiveStyles = (index: number) => {
        if (active.objects[index] === active.activeObject) {
            return "active"
        } else {
            return "not-active"
        }
    }

    return (
        <> <div className="btn-group">
            {active.objects.map((inner, index) => 
                <button type="button" className={toggleActiveStyles(index)} onClick={() => inner.function(index)} key={inner.id}>{inner.title}</button>
            )}
        </div>
        </>

    )
}

export default ButtonGroup;

interface

export interface ActiveObject {
    id: number;
    title: string;
    function(index: number): void;
};

export interface ButtonGroupState {
    activeObject: ActiveObject | null;
    objects: ActiveObject[];
};

component usage so far:

<ButtonGroup />

Solution

Is there any reason each of your data objects has to carry a function around with it to update state? In your example, you’re onClick handler passes the index to a function in your data object that finally passes the index to your state setter. Why not just cut out the middle man and update the state directly in your onClick handler?

I think something like this should do the trick, given I’m understanding your question correctly:

const ButtonGroup: React.FunctionComponent<{ data: ActiveObject[] }> = ({ data: objects }) => {
  const [activeIdx, setActiveIdx] = React.useState(0);

  return (
    <div className="btn-group">
      {objects.map((inner, index) => (
        <button
          type="button"
          className={activeIdx === index ? "active" : "not-active"}
          onClick={() => {
            setActiveIdx(index)
            // You can pass the index, the event, or whatever you want here
            inner.cb(index)
          }}
          key={inner.id}
        >
          {inner.title}
        </button>
      ))}
    </div>
  );
};

Which can then be reused wherever you want, provided you supply the data prop correctly:

const objects: ActiveObject[] = [
  {
    id: 1,
    title: "1 element",
    cb: (idx) => console.log(idx)
  },
  {
    id: 2,
    title: "2 element",
    cb: (idx) => console.log(idx)
  }
];

<ButtonGroup data={objects} />

Or, here’s a full working example on codesandbox.

Answered By – Sam Gomena

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published