Home > Software design >  Handling state of multiple instances of same component
Handling state of multiple instances of same component

Time:10-26

I have this Item component that I am using in another component:

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

const Item = ({ name }) => {
  const [isSelected, setIsSelected] = useState(false);

  const toggle = () => {
    setIsSelected(!isSelected);
  };

  var buttonColor;
  var buttonText;

  if (isSelected === true) {
    buttonColor = "bg-button-blue";
    buttonText = "Selected";
  } else {
    buttonColor = "bg-button-gray";
    buttonText = "Select";
  }

  return (
    <div onClick={toggle} className="flex ml-2 items-center">
      <div className="text-misc-gray text-xs w-40">{name}</div>
      <div>
        <Button
          text={buttonText}
          color={buttonColor}
          height={"h-8"}
          width={"w-18"}
        ></Button>
      </div>
    </div>
  );
};

export default Item;

In the other component, I have multiple instances of this Item component, representing different items. The Item component can with a click change property, like text and color for the button. The problem is that in the other component, multiple of these Items can be toggled at the same time.

I would like that out of every instance of the Item component in the other component, only a single one can be toggled on at the same time. So if I select one item, the previous (if any selected) will be "unselected", changing the text and color back to the original state.

Can this be solved by only making changes in the Item component, or do I also need to make changes where it's being imported?

CodePudding user response:

Can this be solved by only making changes in the Item component

No good way using the React paradigm, because you want one instance to affect another instance, where the other instance is not a child.

In the ancestor component of the <Item>s, create a state variable that holds the index (or ID, or name, or some other uniquely identifying aspect) of the Item component currently toggled on, and then pass down an isSelected prop to each Item, as well as the state setter. (The individual Items should no longer have an isSelected state.) Perhaps something along the lines of:

const numItems = 5;
const Parent = () => {
  const [selectedIndex, setSelectedIndex] = useState(-1);
  const makeToggle = (i) => () => {
    setSelectedIndex(i === selectedIndex ? -1 : i);
  };
  return (
    <div>
      { Array.from(
        { length: numItems },
        (_, i) => <Item
                    isSelected={selectedIndex == i}
                    toggle={makeToggle(i)}
                  />
      )}
    </div>
  );
};

CodePudding user response:

Can this be solved by only making changes in the Item component

Isolating state is good, but in this situation, Item state has dependencies on other components, so we cannot isolate that state completely.

I'd suggest that you should lift your state isSelected up to the parent component, and pass that state down to each Item for UI update.

import React, { useState } from "react";

const ParentComponent = () => {
  const [selectedIndex, setSelectedIndex] = useState();

  //[...Array(5)] is representing your actual items
  return (
    <div>
      [...Array(5)].map((value, index) => <Item key={index} isSelected={selectedIndex === index} index={index} toggle={(i) => setSelectedIndex(i)} />)
    </div>
  );
};

And then change Item props with a little logic modification

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

const Item = ({ name, isSelected, index, toggle }) => {

  var buttonColor;
  var buttonText;

  if (isSelected === true) {
    buttonColor = "bg-button-blue";
    buttonText = "Selected";
  } else {
    buttonColor = "bg-button-gray";
    buttonText = "Select";
  }

  return (
    <div onClick={() => toggle(index)} className="flex ml-2 items-center">
      <div className="text-misc-gray text-xs w-40">{name}</div>
      <div>
        <Button
          text={buttonText}
          color={buttonColor}
          height={"h-8"}
          width={"w-18"}
        ></Button>
      </div>
    </div>
  );
};

export default Item;

  • Related