clicked button toggle all the items in the list (React)

I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.

I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.

readMore is used to hide/show the content and activeIndex is used to keep track of clicked item index.

This is what I have done so far:

import React, { useState } from "react";

const Jobs = ({ data }) => {
  const [readMore, setReadMore] = useState(false);
  const [activeIndex, setActiveIndex] = useState(null);

  const job = data.map((job, index) => {
    const { id, description } = job;
    return (
      <article key={id}>
        <p>
          {readMore ? description : `${description.substring(0, 250)}...`}
          <button
            id={id}
            onClick={() => {
              setActiveIndex(index);
              if (activeIndex === id) {
                setReadMore(!readMore);
              }
            }}
          >
            {readMore ? "show less" : "show more"}
          </button>
        </p>
      </article>
    );
  });

  return <div>{job}</div>;
};

export default Jobs;

The problem is that when I click one button it toggles all the items in the list. I want to show/hide content only when its own button clicked.

Can somebody tell me what I am doing wrong? Thanks in advance.

Answer

Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!

import React, { useState } from "react";

const Jobs = ({ data }) => {
  const [activeIndex, setActiveIndex] = useState(null);

  const job = data.map((job, index) => {
    const { id, description } = job;
    return (
      <article key={id}>
        <p>
          {activeIndex === index ? description : `${description.substring(0, 250)}...`}
          <button
            id={id}
            onClick={() => {
              if (activeIndex) {
                setActiveIndex(null);
              } else {
                setActiveIndex(index);
              }
            }}
          >
            {activeIndex === index ? "show less" : "show more"}
          </button>
        </p>
      </article>
    );
  });

  return <div>{job}</div>;
};

export default Jobs;

Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:

import React, { useState } from "react";

const Jobs = ({ data }) => {
  const [activeIndices, setActiveIndices] = useState(new Set());

  const job = data.map((job, index) => {
    const { id, description } = job;
    return (
      <article key={id}>
        <p>
          {activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
          <button
            id={id}
            onClick={() => {
              const newIndices = new Set(activeIndices);
              if (activeIndices.has(index)) {
                newIndices.delete(index);
              } else {
                newIndices.add(index);
              }
              setActiveIndices(newIndices);
            }}
          >
            {activeIndices.has(index) ? "show less" : "show more"}
          </button>
        </p>
      </article>
    );
  });

  return <div>{job}</div>;
};

export default Jobs;

Leave a Reply

Your email address will not be published. Required fields are marked *