Props is undefined when passed to a child component (react hooks)

Viewed 1681

I am building a simple agenda component and ran into a problem. The idea is that when a person clicks on the day and then sees trainings from this specific day. My logic is the following

  1. On button click I set state to day id
  2. On existing active item Ternary operator renders the component
  3. I am passing function invocation as props, which returns up-to-date object.

I tried putting function invocation to handleClick function, which did not help. For me it seems that the problem can occur with function not returning the value in time for component to pass it, but I don't know how to bypass this problem. Here is the codesandbox with everything - please help

https://codesandbox.io/s/cranky-johnson-s2dj3?file=/src/scheduledTrainingCard.js

Here is the code to parent component, as the problem is here

import React, { useState } from "react";
import { Transition, Grid, Button, Container } from "semantic-ui-react";
import ScheduledTrainingCard from "./scheduledTrainingCard";

function ScheduleComponent(props) {
  const days = [
    {
      id: "1",
      dayTrainings: [
        {
          time: "20:15:00",
          training: "zumba",
          trainer: "joe"
        },
        {
          time: "16:00:00",
          training: "stretching",
          trainer: "lily"
        }
      ],
      date:
        "Thu Dec 28 1995 00:00:00 GMT+0100 (Central European Standard Time)",
      createdAt: "2021-07-14T19:30:59.177Z"
    },
    {
      id: "2",
      dayTrainings: [
        {
          time: "23:15:00",
          training: "boxing",
          trainer: "phoebe"
        },
        {
          time: "15:00:00",
          training: "dancing",
          trainer: "kate"
        }
      ],
      date: "Thu Sep 23 2021 20:57:38 GMT+0200 (Central European Summer Time)",
      createdAt: "2021-09-23T19:01:53.801Z"
    },
    {
      id: "3",
      dayTrainings: [
        {
          time: "23:15:00",
          training: "ballet",
          trainer: "nataly"
        },
        {
          time: "12:00:00",
          training: "crossfit",
          trainer: "sheldon"
        }
      ],
      date: "Fri Jul 23 2021 21:02:37 GMT+0200 (Central European Summer Time)",
      createdAt: "2021-09-23T19:03:31.161Z"
    }
  ];

  const [activeItem, setActiveItem] = useState("");

  const handleItemClick = (e) => {
    setActiveItem(e.target.name);
  };

  function showSelectedDay(arr, dayId) {
    arr.forEach((element, index) => {
      if (dayId === element.id) {
        console.log("selected day is " + element.date);
        let trainings = element.dayTrainings;
        trainings.map((training) => {
          return training;
        });
      } else {
        console.log("not selected, day id is " + element.id);
      }
    });
  }

  const ScheduleComponent = (
      <Container style={{ textAlign: "left" }}>
        <h1 style={{ textAlign: "center" }}>Training Schedule</h1>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt
          dolor unde repudiandae culpa ullam, asperiores officiis ratione
          repellat quaerat nihil vel corporis distinctio vero doloribus dolore
          optio commodi voluptatum inventore.
        </p>

        <Container>
          <Grid style={{ margin: "2rem auto", textAlign: "center" }} relaxed>
            <Button onClick={(e) => handleItemClick(e)} name={"1"}>
              Monday
            </Button>
            <Button onClick={(e) => handleItemClick(e)} name={"2"}>
              Tuesday
            </Button>
            <Button onClick={(e) => handleItemClick(e)} name={"3"}>
              Wednesday
            </Button>
          </Grid>
        </Container>
        <Container>
          {activeItem && (
            <ScheduledTrainingCard
              dayTraining={showSelectedDay(days, activeItem)}
            ></ScheduledTrainingCard>
          )}
        </Container>
      </Container>
   
  );
  return ScheduleComponent;
}

export default ScheduleComponent;

3 Answers

You can do this

const handleItemClick = (e) => {
    days.map(item => {
      if(item.id === e.target.name){
        setActiveItem(item)
      }
    })
  };

and change this line

{activeItem && 
          activeItem.dayTrainings.map(day => <ScheduledTrainingCard dayTraining={day}/>)
          }

First define a new state for selectedDays:

  const [activeItem, setActiveItem] = useState("");
  const [selectedDays, setSelectedDays] = useState([]);

Then set value for setSelectedDays based on user's choice and activeItem:

  useEffect(() => {
    let filteredDays = days.filter((x) => x.id === activeItem);
    setSelectedDays(filteredDays);
  }, [activeItem]);

Finally define a new function to render the days. Because days is a hierarchy array, you need to loop through it twice:

  const showDays = () => {
    return selectedDays.map((days, index) => {
      return days.dayTrainings.map((item, i) => {
        let dayTraining = {
          id: days.id,
          time: item.time,
          trainer: item.trainer,
          training: item.training
        };
        return (
          <ScheduledTrainingCard
            dayTrainings={dayTraining}
          ></ScheduledTrainingCard>
        );
      });
    });
  };

const ScheduleComponent = (
    <Transition.Group>
      <Container style={{ textAlign: "left" }}>
        <h1 style={{ textAlign: "center" }}>Training Schedule</h1>
        <p>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt
          dolor unde repudiandae culpa ullam, asperiores officiis ratione
          repellat quaerat nihil vel corporis distinctio vero doloribus dolore
          optio commodi voluptatum inventore.
        </p>

        <Container>
          <Grid style={{ margin: "2rem auto", textAlign: "center" }} relaxed>
            <Button onClick={(e) => handleItemClick(e)} name={1}>
              Monday
            </Button>
            <Button onClick={(e) => handleItemClick(e)} name={2}>
              Tuesday
            </Button>
            <Button onClick={(e) => handleItemClick(e)} name={3}>
              Wednesday
            </Button>
          </Grid>
        </Container>
        <Container>{activeItem && selectedDays && showDays()}</Container>
      </Container>
    </Transition.Group>
  );
  return ScheduleComponent;

Edit blazing-dawn-pmixb

The problem is that you are using return inside map. Look how I did it:

1- We don't need the function showSelectedDay so I remove it.

2- We don't need the state const [activeItem, setActiveItem] = useState("");.

3- Add new state const [dayTrainings, setDayTrainings] = useState([]);

4- Update handleItemClick to:

  const [dayTrainings, setDayTrainings] = useState([]);
  const handleItemClick = (e) => {
    days.forEach((element, index) => {
      if (e.target.name === element.id) {
        console.log("selected day is " + element.date);
        setDayTrainings(element.dayTrainings);
      } else {
        console.log("not selected, day id is " + element.id);
      }
    });
  };

5- In the render return:

      {dayTrainings.length > 0 && (
        <ScheduledTrainingCard
          dayTraining={dayTrainings}
        ></ScheduledTrainingCard>
      )

Example of ScheduledTrainingCard:

export default function scheduledTrainingCard(props) {
  console.log(props.dayTraining);
  return (<>
      {
        props.dayTraining.map((item, index) => 
        <p key={index}>
          {item['time']}<br/>
          {item['training']}<br/>
          {item['trainer']}<br/>
          <br/><br/>
        </p>)
      }
    </>);
}

Example of output: output

Related