Home > Software engineering >  React.js: How to expand only the content of the clicked property?
React.js: How to expand only the content of the clicked property?

Time:10-25

I am trying to expand/collapse the content of the clicked property, but clicking expands all the properties.
I understand that I need to expand the item based on some indentifier, like if the idx of icon is the same as the idx of the item, then expand only that item, but can't implement in code.

Here is the codesandbox link and code example below
App.js

import "./styles.css";
import ArticleItem from "./ArticleItem";
import { useState } from "react";

const ARTICLES = [
  {
    title: "What's SAP",
    upvotes: 1,
    date: "2019-11-21"
  },
  {
    title: "Simple text editor has 15k monthly users",
    upvotes: 7,
    date: "2010-12-31"
  }
];

export default function App() {
  const [isOpen, setIsOpen] = useState(false);

  const toggleView = (idx) => {
    return ARTICLES.map((_, index) => {
      if (idx === index) {
        setIsOpen(!isOpen);
      }
    });
  };

  return (
    <div className="App">
      {ARTICLES.map((article, idx) => (
        <div>
          {article.title}
          <button onClick={() => toggleView(idx)}>
            <i className="fas fa-arrow-down"></i>
          </button>
          {isOpen && <ArticleItem article={article} />}
        </div>
      ))}
    </div>
  );
}

ArticleItem.js

import React from "react";

const ArticleItem = ({ article }) => {
  return (
    <>
      <div>{article.upvotes}</div>
      <div>{article.date}</div>
    </>
  );
};

export default ArticleItem;

Any help will be appreciated

CodePudding user response:

Currently you have one boolean value:

const [isOpen, setIsOpen] = useState(false);

This boolean value doesn't store the information of which article to display, only to either display or not display.

I understand that I need to expand the item based on some indentifier

Then store an identifier instead of just a boolean. For example, if you're using the index position as your identifier, store that. Start with some default:

const [openArticle, setOpenArticle] = useState(-1);

Since -1 won't match any index, none of them will be open by default. Use that value to drive the rendering:

{openArticle === idx && <ArticleItem article={article} />}

And update that value in your click handler:

const toggleView = (idx) => {
  setOpenArticle(idx);
};

If re-clicking the same index should close it, add that logic as well:

const toggleView = (idx) => {
  if (openArticle === idx) {
    setOpenArticle(-1);
  } else {
    setOpenArticle(idx);
  }
};

CodePudding user response:

Each article should be its own component with its own state:

      {ARTICLES.map((article, i) => (
        <ArticleItem key={i} article={article} />
      ))}

Generally if you find that the parent must manage a child's state, you probably have something wrong and you should move the state of the child into the child itself.

So taking that into consideration, I have moved the isOpen state to the ArticleItem component:

const ArticleItem = ({ article }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      {article.title}
      <button onClick={() => setIsOpen(!isOpen)}>
        <i className="fas fa-arrow-down"></i>
      </button>
      {isOpen && (
        <>
          <div>{article.upvotes}</div>
          <div>{article.date}</div>
        </>
      )}
    </div>
  );
};

Now the button will expand and only expand the article it is for.

codesandbox

CodePudding user response:

Have each child manage its own state of open or closed.

export default function App() {
  const ARTICLES = [
   ...
  ];

  let articles = ARTICLES.map((i) => {
    return (
      <ArticleItem
        key={i.id}
        title={i.title}
        upvotes={i.upvotes}
        date={i.date}
      />
    );
  });

  return <div className="App">{articles}</div>;
}

Then in the child:

const ArticleItem = (props) => {
  const [isOpen, setIsOpen] = useState(false);

  const toggleView = () => {
    setIsOpen(!isOpen);
  };

  return (
    <>
      <div>{props.title}</div>
      {isOpen && (
        <>
          <div>{props.upvotes}</div>
          <div>{props.date}</div>
        </>
      )}
      <button onClick={() => toggleView()}>
        <i className="fas fa-arrow-down"></i>
      </button>
    </>
  );
};

export default ArticleItem;
  • Related