Home > Back-end >  React: How do I only render a component on the page once I click on it from a mapped array?
React: How do I only render a component on the page once I click on it from a mapped array?

Time:11-10

I've got a grid of components rendered on a page using the map function and I want to only render the component with all it's details once I click on it and only manage to get an error saying:

Uncaught TypeError: Cannot read properties of undefined (reading 'logo')

I'm new to React and can't figure out why this is happening.

Some help would be much appreciated!

Here's my code:

Body component - (parent component)

import { useState } from "react";
import CompanyList from "./CompanyList";
import Company from "./Company";

const Body = ({ companies }) => {
  const [viewCompany, setViewCompany] = useState(false);
  const showCompanyHandler = (company) => {
    console.log("Clicked on company card");
    setViewCompany(true);
  };

  return (
    <div className="appBody">
      <CompanyList
        companies={companies}
        showCompanyHandler={showCompanyHandler}
      />
      {viewCompany && <Company />}
    </div>
  );
};

export default Body;
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CompanyList component (child of Body)

import Company from "./Company";

const CompanyList = ({ companies, showCompanyHandler }) => {
  return (
    <div className="companyList">
      {companies.map((company) => (
        <Company
          key={company.id}
          company={company}
          showCompanyHandler={showCompanyHandler}
        />
      ))}
    </div>
  );
};

export default CompanyList;
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Company component (child of CompanyList) - Component I want to display on the page

const Company = ({ company, showCompanyHandler }) => {
  return (
    <div className="company" onClick={(company) => showCompanyHandler(company)}>
      {console.log(company)}
      <img src={company.logo} alt="logo" />
      <h1>{company.name}</h1>
      <p>{company.companyDescription}</p>
    </div>
  );
};

export default Company;
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I'm not sure what I'm missing here...

CodePudding user response:

If I understood correctly, your code structure is broken and you need to refactor it to implement the kind of logic that you're looking for.

First of all, I would suggest that you remove this {viewCompany && <Company />}, viewCompany and showCompanyHandler from your Body component.

Since the Company component will always be visible, IMO it should be responsible for handling this "expand" logic that you're trying to implement, something like:

const Company = ({ company }) => {
  const [showDetails, setShowDetails] = useState(false);
  const toggleShowDetails = () => setShowDetails(!showDetails);
  return (
    <div className="company" onClick={toggleShowDetails}>
      {showDetails &&
      <>
        <img src={company.logo} alt="logo" />
        <h1>{company.name}</h1>
        <p>{company.companyDescription}</p>
      </>
    }
      </div>
  );
};

export default Company;
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

As you can see, with this implementation you no longer have to pass showCompanyHandler to the Company component and you can remove all of this logic from the Body component.

CodePudding user response:

If CompanyList component is working fine, an easy way to do it would be to use a function to dynamically generate the list instead of having a direct reference to the component.

import { useState } from "react";
import CompanyList from "./CompanyList";
import Company from "./Company";

const Body = ({ companies }) => {
  const [viewCompany, setViewCompany] = useState(false);

  const showCompanyHandler = () => {
    console.log("Clicked on company card");
    setViewCompany(true);
  };

  // I removed the showCompanyHandler attribute and 
  // added an onClick to CompanyListContainer, instead.
  const generateCompanyList = (companies) => {
    if (viewCompany === false) return null;
    return <CompanyList companies={companies} />
    )
  }

  return (
    <div className="appBody">
      <CompanyListContainer onClick={showCompanyHandler}>
          {generateCompanyList}
      </CompanyList>
    </div>
  );
};

export default Body;

If generateCompanyList is null, there should be nothing to click on, unfortunately. So, you could add some styles to it the div for a minimum width/height, or, you can move that onClick to your appBody component.

  • Related