How can I render an icon dynamically?

Viewed 470

How can I render only the icon cartIcon dynamically? Because right now, like the code below, when I enter in the component with the mouse, all the icons appears not only the icon of the single product.

I think because of map but how can I render only to it?

interface IItemsProps {
  products: ProductsType;
}

const Items: React.FunctionComponent<IItemsProps> = ({ products }) => {
  const [state, setState] = React.useState<boolean>(false);

  const handleMouseEnter = () => {
    setState(true);
  };

  const handleMouseLeave = () => {
    setState(false);
  };

  const itemUI = products.map((item: SingleProductsType) => {
    const { name, price, _id } = item;

    return (
      <WrapperSingleItem key={uuidv4()} id={_id}>
        {state && <IconsCarts />} ** //HERE I NEED TO SHOW THIS COMPONENT ONLY WHEN I 
                                   // ENTER WITH THE MOUSE BUT ONLY FOR THE SELECTED 
                                    //PRODUCT NOT ALL OF THEM **
        <ImgProduct
          src={mouse}
          alt={name}
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
        />
        <WrapperTextProduct>
          <TextName>{name}</TextName>
          <div>
            <TextActualPrice>$ {price}</TextActualPrice>
            <TextPreviousPrice>
              $ {Math.trunc((price * 20) / 100 + price)}.00
            </TextPreviousPrice>
          </div>
        </WrapperTextProduct>
      </WrapperSingleItem>
    );
  });

  return <WrapperItems>{itemUI}</WrapperItems>;
};

export default Items;
2 Answers

You could store the hovered _id in state, so you know which one it was.

const [state, setState] = React.useState<string | null>(null); // or `number` ?

Then

{state === _id && <IconsCarts />}

<ImgProduct
  src={mouse}
  alt={name}
  onMouseEnter={() => setState(_id)}
  onMouseLeave={() => setState(null)}
/>

Or you could move the useState into a component that is called every loop of your map, so that each item has its own private state.

function MyItem({item}: { item: SingleProductsType }) {
    const [state, setState] = React.useState<boolean>(false);

    const { name, price, _id } = item;

    return (
      <WrapperSingleItem key={uuidv4()} id={_id}>
        {state && <IconsCarts />}

        <ImgProduct
          src={mouse}
          alt={name}
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
        />
        <WrapperTextProduct>
          <TextName>{name}</TextName>
          <div>
            <TextActualPrice>$ {price}</TextActualPrice>
            <TextPreviousPrice>
              $ {Math.trunc((price * 20) / 100 + price)}.00
            </TextPreviousPrice>
          </div>
        </WrapperTextProduct>
      </WrapperSingleItem>
    );
}

Now you can do:

{products.map((item: SingleProductsType) => <MyItem item={item} />}

Lastly, if all you want to do is show/hide the cart icon when you enter some element with the mouse, this solution is probably way overkill. You can do this with CSS alone, which is going to be a far cleaner solution since it takes no javascript code whatsoever, and you don't have to track state at all.

.item {
  width: 100px;
  height: 100px;
  background: #aaa;
  margin: 10px;
}

.item button {
  display: none;
}

.item:hover button {
  display: block;
}
<div class="item">
  Foo
  <button>Add to cart</button>
</div>
<div class="item">
  Bar
  <button>Add to cart</button>
</div>
<div class="item">
  Baz
  <button>Add to cart</button>
</div>

With a boolean in state, all you know is whether to show an icon, but what about knowing which list item to show the icon on? Instead of state being a boolean, how about we use the index of the product.

interface IItemsProps {
  products: ProductsType;
}

const Items: React.FunctionComponent<IItemsProps> = ({ products }) => {
  const [state, setState] = React.useState<number>(-1);

  const handleMouseEnter = (index) => {
    setState(index);
  };

  const handleMouseLeave = () => {
    setState(-1);
  };

  const itemUI = products.map((item: SingleProductsType, index: number) => {
    const { name, price, _id } = item;

    return (
      <WrapperSingleItem key={uuidv4()} id={_id}>
        {state === index && <IconsCarts />} ** //Check if index matches state before showing icon **
        <ImgProduct
          src={mouse}
          alt={name}
          onMouseEnter={() => handleMouseEnter(index)}
          onMouseLeave={handleMouseLeave}
        />
        <WrapperTextProduct>
          <TextName>{name}</TextName>
          <div>
            <TextActualPrice>$ {price}</TextActualPrice>
            <TextPreviousPrice>
              $ {Math.trunc((price * 20) / 100 + price)}.00
            </TextPreviousPrice>
          </div>
        </WrapperTextProduct>
      </WrapperSingleItem>
    );
  });

  return <WrapperItems>{itemUI}</WrapperItems>;
};

export default Items;

Now the condition to show the icon is if the index of the list item matches the index in state. And we pass in the index to handleMouseEnter to set state to that index, and handleMouseLeave will reset it back to -1.

Related