Search code examples
reactjsflexboxternary

Ternary operator doesn't return react component


I'm not quite sure that the problem so I will explain it a bit wider.

I'm trying to make react schedule component, like on this image: react schedule

The problem I get is that I'm getting empty flex components with no elements in them.

So I have a JSON file with data. It looks like this:

export const eventHours = [
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,]

export const scheduleData = [
{
    weekDay: {
        title: 'Day 1',
        day: 'Monday',
        date: 'NOvemb 30th',
    },
    events: [
        {
            icon: '/images/icons/talk.png',
            type: 'Lecture',
            title: 'adsasd',
            startTime: 11,
            endTime: 14,
        },
    ],
},
{
    weekDay: {
        title: 'Day 2',
        day: 'Monday',
        date: 'NOvemb 30th',
    },
    events: [
        {
            
            icon: '/images/icons/talk.png',
            type: 'Lecture',
            title: 'adsasd',
            startTime: 11,
            endTime: 14,
        },
        {
            
            icon: '/images/icons/talk.png',
            type: 'Lecture',
            title: 'adsasd',
            startTime: 16,
            endTime: 18,
        },
    ],
},]

So the idea I had is to map through days, for each day I render the Flex component (I'm using ChakraUI), then inside that component, I'm mapping through all events for that day. For each event, I'm mapping through hours. If the hour is less than event startTime I'm returning true if it's equal then return true with that I'm making a boolean array.

Finally, I'm mapping through that array on the true return empty box component making empty spaces, and on false return actual cell component.

Here is code to make it easy to understand

events component: I'm getting rendered two of them for 2 days from data

const Events = (props: Props) => {
return (
    <>
         <Box w='1000px' h='510px'>
        {scheduleData.map((day) => <EventFlex events={day.events} />)}
     </Box> 
    </>
)}

eventFlex component here is happening all logic

I'm not getting anything rendered, the parent div is empty.

interface Props {
    events: Event[]
}
type Event = {
    icon: string
    type: string
    title: string
    startTime: number
    endTime: number
}

const EventFlex = ({ events }: Props) => {

    const [emptyEventCells, setEmptyEventCells] = useState<boolean[]>([])
    const [myEvents, setEvents] = useState<Event[]>([])

    useEffect(() => {
        setEvents(events)
        generateEmptyEventCells()

    }, [])
    const generateEmptyEventCells = () => {
        let tempFlexItems: boolean[] = []

        myEvents.map(event => {
            eventHours.map(hour => {

                if (hour < event.startTime) {
                    tempFlexItems.push(true)
                }
                if (hour === event.startTime) {
                    tempFlexItems.push(false)
                }
            })
        })
        setEmptyEventCells(tempFlexItems)
    }
    const calculateEventLength = (startTime: number, endTime: number) => {
        return endTime - startTime;
    };

    return (<Flex h='102px'>
        {emptyEventCells.map((isEmpty: boolean, index) => (
            <React.Fragment>
                {isEmpty ? <Box w="83px"></Box> : <EventCell
                    size={`${calculateEventLength(myEvents[index].startTime, myEvents[index].endTime) * 83}`}
                    icon={myEvents[index].icon}
                    type={myEvents[index].type}
                    title={myEvents[index].title}
                    time={`${myEvents[index].startTime}h-${myEvents[index].endTime}h`}
                />}
            </React.Fragment>
        ))}
    </Flex>);

};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


Solution

  • Ternary operator is working as expected, but I found 2 errors and 1 issue with your code.

    1. wrong usage of useEffect. Since setEvents is asynchronous, calling generateEmptyEventCells after setEvents(events) won't give expected result. Break it into 2 useEffects

    2. Length of emptyEventCells is not same as myEvents, hence, in the EventFlex component you should not use index from emptyEventCells as myEvents[index]. If you want to know the event related to isEmpty value, better find another way like this.

    3. Issue: If rendering elements from an array, like using Array.map, you should provide key prop to the Element.

    Final code is

    EventFlex Component

    interface Props {
      events: Event[];
    }
    
    type Event = {
      icon: string;
      type: string;
      title: string;
      startTime: number;
      endTime: number;
    };
    
    type CellInfo = {
      isEmpty: boolean;
      event: Event;
    };
    
    const EventFlex = ({ events }: Props) => {
      const [emptyEventCells, setEmptyEventCells] = useState<CellInfo[]>([]);
      const [myEvents, setEvents] = useState<Event[]>([]);
    
      useEffect(() => {
        setEvents(events);
      }, []);
      useEffect(() => {
        generateEmptyEventCells();
      }, [myEvents]);
      const generateEmptyEventCells = () => {
        let tempFlexItems: CellInfo[] = [];
    
        myEvents.forEach((event) => {
          // use forEach if not returning anything
          eventHours.forEach((hour) => {
            // use forEach if not returning anything
            if (hour < event.startTime) {
              tempFlexItems.push({ isEmpty: true, event });
            }
            if (hour === event.startTime) {
              tempFlexItems.push({ isEmpty: false, event });
            }
          });
        });
        setEmptyEventCells(tempFlexItems);
      };
      const calculateEventLength = (startTime: number, endTime: number) => {
        return endTime - startTime;
      };
      return (
        <Flex h="102px">
          {emptyEventCells.map(({ isEmpty, event }, index) => (
            <React.Fragment key={`${index}`}>
              {isEmpty ? (
                <Box w="83px"></Box>
              ) : (
                <EventCell
                  size={`${
                    calculateEventLength(event.startTime, event.endTime) * 83
                  }`}
                  icon={event.icon}
                  type={event.type}
                  title={event.title}
                  time={`${event.startTime}h-${event.endTime}h`}
                />
              )}
            </React.Fragment>
          ))}
        </Flex>
      );
    };
    

    Events Component

    const Events = (props: Props) => {
      return (
        <>
          <Box w="1000px" h="510px">
            {scheduleData.map((day, index) => (
              <EventFlex events={day.events} key={`${index}`} /> // use key
            ))}
          </Box>
        </>
      );
    };
    

    Here is the codesandbox demo: https://codesandbox.io/s/young-firefly-cxvbh?file=/src/App.tsx