Search code examples
reactjsreact-hooksreact-state-managementreact-state

When I set state via useState in parent component, my props in the child I'm passing down to are undefined


I have a parent component Listings, that has an array of objects from a seed file. I only want to load the first 9 of 14 objects on page load.

const Listings = () => {
  const [ listingsData, setListings] = useState(listings);

  const [ homesDisplayed , setDisplayNumber ] = useState(listingsData.slice(0, 9));
  const homes = homesDisplayed.map((home, index) => (
    <div className="col xl4" key={index}>
      <HomeListingCard 
        name={home.homeName}
        imageUrl={home.imageURL}
        beds={home.beds}
        baths={home.baths}
        isMultiSection={home.isMultiSection}
        sqft={home.sqft}
        startingPrice={home.startingPrice}
      />
    </div>
  ));

  return (
    <div>
      <div className="row">
        <div className="listings-subheader">
          <h3 className="left available-homes">{`${listingsData.length} `}homes available</h3>
          <div className="right">
            <SortBy />
          </div>
        </div>
      </div>
      <div className="row">
        {homes}
      </div>
      <div className="row">
        <LoadListings listings={listingsData} homesDisplayed={homes} setDisplayNumber={setDisplayNumber} />
      </div>
    </div>
  )
}

export default Listings;

I use const [ homesDisplayed , setDisplayNumber ] = useState(listingsData.slice(0, 9)); to set an array of 9 objects to state.

This works fine.

Then I am trying to pass my state down to a child component as props like this:

<LoadListings listings={listingsData} homesDisplayed={homes} setDisplayNumber={setDisplayNumber} />

When the page initially loads, those props have data. But when I click a button in the <LoadListings /> child component to console.log(props), my data is all undefined.

<LoadListings /> looks like:

const LoadListings = (props) => {
    const updateListings = (props) => {
        console.log(props.homes);
        // props.setDisplayNumber(...props.homesDisplayed, props.listings.slice(9, 12));
    }

    return (
        <button onClick={updateListings}>Load More</button>
    )
}

My goal is to click the button and have updateLIstings splice 3 more off the original array of 14 and add them to homesDisplayed array like so

const updateListings = (props) => {
        props.setDisplayNumber(...props.homesDisplayed, props.listings.slice(9, 12));
}

but props.homesDisplayed -- and all props -- are undefined.

Thank you.


Solution

  • This is happening because you're not actually passing your props down to the updateListings function.

    If you have a closer look at your function declaration, you'll notice that you've defined props as a parameter. Then, on the onClick attribute, you're not actually passing anything to the function.

    Think about the scope of the updateListings function... You don't actually need to pass it anything, as it already has access to props from the parent scope. Unfortunately, by defining props a second time as a function parameter, you're shadowing the higher-scope props (effectively telling the function to ignore it).

    If you update updateListings to omit the props argument, it should work (barring any other problems in your code).

    const updateListings = (props) => {
      console.log(props.homes);
      props.setDisplayNumber(...props.homesDisplayed, props.listings.slice(9, 12));
    }
    

    Side note: Instead of storing the sliced data in state, you may want to just store the number of items you intend to show. That way, instead of reasoning about the data in state, you can simplify everything by just performing the slice in the render itself. For example:

    const [ listingsData, setListings] = useState(listings);
    
    // Store number to display
    const [ homesDisplayed , setDisplayNumber ] = useState(9);
    
    // Slice before map, which'll update every time you increment `homesDisplayed
    const homes = homesDisplayed.slice(0, homesDisplayed).map((home, index) => (
      <div className="col xl4" key={index}>
        <HomeListingCard 
          name={home.homeName}
          imageUrl={home.imageURL}
          beds={home.beds}
          baths={home.baths}
          isMultiSection={home.isMultiSection}
          sqft={home.sqft}
          startingPrice={home.startingPrice}
        />
      </div>
    ));
    

    Another side note: typically you'll want to name your [state, setState] val/updater consistently following the convention of varName, setVarName. That way it's less confusing to the reader (and you!) later on when you're trying to reconcile what controls what.

    So I'd suggest changing [homesDisplayed, setDisplayNumber] to [displayNumber, setDisplayNumber].