简体   繁体   中英

When a react component is clicked, how do I hide all other instances of that component?

I'm working on my portfolio as I'm a jobless junior currently and I'm finding it practically impossible to get all instances of a component to disappear upon the interaction (click) of another instance of said component.

I essentially have cards which were created with react-card-flip; when the user clicks on one of these cards it will flip and expand in size to show more details. There are multiple instances of these cards which are created through an axios GET request to api.github... The part I have not managed to get working is making the rest of the cards disappear upon expansion of the clicked card

I've read that in React you shouldn't be accessing elements using querySelector.. but even using that I can still not get it to work.

I would prefer not to completely refactor my code which I'll add below:

Projects Page:

const Projects = () => {
  let cards = document.getElementsByClassName("list")
  const [Repos, setRepos] = useState([]);
  const Hider = (id) => {
    console.log(cards)
    // cards.classList.add('hidden')
  }
  useEffect(() => {
    axios.get("https://api.github.com/users/Leehamb99/repos").then((response) => {
      setRepos(response.data)


    })
      .catch(err => {
        console.log(err)
      })

  }, []);

  return (
    <>
      {Repos.map((repo, index) => {
        return (
          <div className="list" onClick={() => { Hider(index) }}>
            <ProjectCard key={index} name={repo.name} />
          </div>
        )
      })}
    </>
  )



};

ProjectCard Component:

const ProjectCard = (props) => {


  const [flip, setFlip] = useState(false);
  return (
    <ReactCardFlip isFlipped={flip}
      flipDirection="vertical">
      <div style={{
        display: 'flex',
        flexWrap: 'wrap',
        width: '75px',
        height: '75px',
        background: 'gray',
        fontSize: '18px',
        color: 'black',
        margin: '15px',
        borderRadius: '4px',
        textAlign: 'center',
        padding: '20px',
        boxShadow: '10px'
      }} onClick={() => setFlip(!flip)}>
        {props.name}
      </div>
      <div style={{
        width: '500px',
        height: '500px',
        background: '#F0EDE4',
        fontSize: '18px',
        color: 'black',
        margin: '15px',
        borderRadius: '4px',
        textAlign: 'center',
        padding: '20px',
        boxShadow: '10px'
      }} onClick={() => setFlip(!flip)}>
        Computer Science Portal.
      </div>
    </ReactCardFlip>
  );
}

(inline styling will be moved to a css soon don't worry) So my approach to this would be to add a class called "hidden" (which has a "display: none" attribute) to all instances of this component apart from the one I have clicked.

Perhaps I'm going about it the wrong way since my front-end knowledge is quite limited.

If you're working on this for your portfolio, then it is every bit more important that you're following good code standards.

In-line style as you're doing it is absolutely terrible. Any half decent engineer will toss out your resume if they see code like that.

Put your styling in a css/scss/etc. file and import it into the component. Or have a base style sheet and do it all in there. Or use styled components.

Or the absolute bare minimum, extract out style variables so the return method is readable. eg:

const style = { ... }

return <div style={style}/>

But I only do this for sparse styling that is conditional on component props/state.

My preference is to have each component in its own folder (in the components folder), and in that folder have index.tsx and styles.scss, where we export the component from index.tsx, and that component imports styles.scss.

For every component. If that makes sense. But there's lots of acceptable ways.

Also, I would consider switching to TypeScript - the strong majority of commercial projects nowadays are using it for their projects.

Anyway, in terms of the issue you have:

You're correct in assuming you never want to use query selectors. Almost never 99.9% of the time.

What you could do to solve this pretty easily is you could move the logic to Projects instead of in the Card component.

so like in Projects:

const [flippedCardIndex, setFlippedCardIndex] = useState(undefined);

Then pass the an onclick function to the Card component, (which calls setFlippedCardIndex(idx) ), along with another prop (eg isFlipped) for if it is or isn't flipped based on whether flippedCardIndex is equal to the index of the card in the.map(). Which you can use to determine if we want to display: hidden if the index does not equal the flippedCardIndex.

and then get rid of the flipped state variable in Card.

Might be a better way, but that will work.

I've simplified this for you. When clicks on a card just show that card without hiding others:

import { ReactElement } from 'react';

import * as React from 'react';

const cards = Array.from({ length: 5 }).map((item, index) => ({
  title: `Item ${index}`,
}));

export default function App(): ReactElement {
  const [showingCard, showCard] = React.useState<number | null>(null);
  function toggleDisplayCard(index) {
    showCard((prev) => (prev === index ? null : index));
  }
  return (
    <div style={{ display: 'flex', gap: '0.5em' }}>
      {showingCard!==null ? (<Card
          isShown={true}
          title={cards[showingCard].title}
          onClick={() => toggleDisplayCard(showingCard)}
        />) : cards.map(({ title }, index: number) => (
        <Card
          title={title}
          onClick={() => toggleDisplayCard(index)}
        />
      ))}
    </div>
  );
}

function Card({
  title,
  onClick,
  isShown
}: {
  isShown?: boolean;
  title: string;
  onClick: any;
}): ReactElement {
  return (
    <div
      style={{
        cursor: 'pointer',
        border: isShown ? '2px solid yellowgreen' : '1px solid silver',
        display: 'flex',
        padding: '1rem',
      }}
      onClick={onClick}
    >
      {title}
    </div>
  );
}

You need memorize to prevent wasted renders using React.memo, React.useMemo, React.useCallback in proper way in a production.

Perhaps this isn't the most efficient way of doing things, since I'm calling an iteration on every click of a card, but it seems to work!

Only took a few lines of code too. Here was my solution:

  const Hider = (id) => {
    click++
    for (let i = 0; i < cards.length; i++) {
      cards[i].classList.add("hidden")
    }
    cards[id].classList.remove("hidden")
    if (click % 2 == 0){
      for (let i = 0; i < cards.length; i++) {
        cards[i].classList.remove("hidden")
      }
    }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM