2

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;

BadgerWannaBe
  • 37
  • 1
  • 8

3 Answers3

1

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}/>)
          }
edhi.uchiha
  • 366
  • 2
  • 9
1

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

Majid M.
  • 4,467
  • 1
  • 11
  • 29
  • Thank you for improving on it, using useEffect is a nice touch, I really like it. I understand if we have state as an array, I can have access to its content till the state changes, is it right? – BadgerWannaBe Sep 26 '21 at 15:39
  • 1
    @BadgerWannaBe Definitely yes! When state changed, you can access it through the new state in all over of the component and you doesn't need to send its value as a parameter to another function inside your component. – Majid M. Sep 26 '21 at 15:47
  • @BadgerWannaBe If the answer was successful you can accept it for further resources. – Majid M. Sep 26 '21 at 15:48
  • Hi Majid, I am testing your answer I ran into another problem - my actual app is using Apolli useQuery hook to get the data, and it seems to me useEffect does not see or get "days" array in time. here how it looks fully `const [activeItem, setActiveItem] = useState(""); const [selectedDays, setSelectedDays] = useState([]); const { loading, data: { getDays: days } = {} } = useQuery(FETCH_DAYS_QUERY); useEffect(() => { let filteredDays = days.filter((x) => x.id === activeItem); console.log(filteredDays); setSelectedDays(filteredDays); }, [activeItem]);` – BadgerWannaBe Sep 26 '21 at 16:31
  • Ok, It seems to me that I found the root of the problem :) https://stackoverflow.com/questions/60008587/usequery-data-undefined-when-calling-useeffect-hook?rq=1 – BadgerWannaBe Sep 26 '21 at 16:39
  • 1
    I haven't tried `useQuery` before! But If you mean you call a api, you just need to call new api when page loaded successfully with `useEffect(( )=>{ },[ ])` and get days, finally define a new state and set the state with api result. So you can use new state instead of static arr. – Majid M. Sep 26 '21 at 16:42
  • Need to try that, according to people here [link](https://github.com/trojanowski/react-apollo-hooks/issues/158) useQuery should fully be used instead of useEffect as, but I can't wrap my head around that at the moment. Will try your suggestion and see what happens – BadgerWannaBe Sep 26 '21 at 17:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237531/discussion-between-badgerwannabe-and-majid-m). – BadgerWannaBe Sep 26 '21 at 17:44
1

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

Abdulmuhaymin
  • 905
  • 7
  • 16
  • Thank you for thorough explanation and improvement at saving array to state. I have questions to the rendering - so in this solution it is rendered when we have more than 0 trainings, I can't find any flaw in it but still not a lot of people use that (in tutorials and other examples). Can't really understand why not. – BadgerWannaBe Sep 26 '21 at 15:52