简体   繁体   中英

Need help figuring out my data manipulation/prop passing React-Native/react-native-calendars

I am using react-native-calendars , aws-amplify , moment and uuid to fetch data, display date data as dots marked on the calendar and then allow the user to open a modal with that dates items from the query in my React Native (Expo) app. I am getting an error, which isn't making much sense to me, I think it's the way I'm handling data manipulation/processing or property passing:

TypeError: undefined is not an object (evaluating 'item.createdAt')

This may be a long explanation, but I will explain to the best of my ability what's happening and where I think the error is coming from.

I have a query that returns a list of items, in this case they are notifications that have been sent to the user. I have this function which handles getting the data and structuring it so that I can pass the items to a sub-component. Here is how I am querying the info and how I'm storing the data.

CalendarCard.js (first half)

import React, { useEffect, useState } from "react";
import moment from 'moment';
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { API } from "aws-amplify";
import { listUserNotifications } from '../../graphql/queries';

...

const CalendarCard = (
    {
        showCalendarInfoModal,
        showDayModal,
        setDayNotificationItems,
    }) => {

    const [markedDates,setMarkedDates] = useState();
    const [notificationItems, setNotificationItems] = useState();

    const currentMonth = moment().startOf('month').toISOString();
    const now = new Date().toISOString();

    useEffect(() => {
        const recordInitNotifications = (notifications) => {
            notifications.forEach((item,i) => {
                let id = uuidv4().toString();
                let createdAt = item.createdAt;
                let notificationDate = moment(createdAt).format('YYYY-MM-DD');
                let notificationId = notificationDate+id;
                
                setMarkedDates(prev => ({
                    ...prev,
                    [notificationDate]: {marked: true, dotColor: 'grey'}
                }));
                setNotificationItems(prev => ({
                    ...prev,
                    [notificationId]: {[i]: item}
                }));
            });
        };

        const currentNotifications = async () => {
            const initNotificationData = await API.graphql({
                query: listUserNotifications,
                variables: {
                    filter: {
                        createdAt: {
                            between: [currentMonth,now]
                        }
                    }
                },
                authMode: 'AMAZON_COGNITO_USER_POOLS'
            });

            const initNotifications = initNotificationData.data.listUserNotifications.items;
            recordInitNotifications(initNotifications);
        };

        currentNotifications().catch(console.error);
    }, []);
...
// continued below

The result of the above sets my notificationItems variable as follows when I log it:

Object {
  "2022-04-096d64e695-c377-4790-88bc-4de803f63028": Object {
    "0": Object {
      "PushToken": "ExponentPushToken[*********************]",
      "StackName": "Everydaytest",
      "StackSchedule": "{\"Everyday\":{\"dw\":[6,7,1,2,3,4,5],\"type\":\"DAY_WEEK\",\"startDate\":\"2022-04-09T15:00:00.000Z\"}}",
      "StackScheduleName": "Everyday",
      "UserID": "user",
      "createdAt": "2022-04-09T15:00:22.504Z",
      "id": "ee520f0a-cd93-47f9-954d-0c5f741daaa7",
      "lastReceipt": "{\"stack\":\"patrick2022-04-09T14:52:37.519Z<139029b5-4402-4c08-b41f-b7653e64cb93>ba7cb97d-d6db-4086-82dd-b6a8c79712e5\",\"ticket\":{\"id\":\"e1ba6799-0a03-4126-a1fc-6fc4f39308d2\",\"status\":\"ok\"}}",
      "lastReceiptCreated": "2022-04-09T15:00:22.504Z",
      "owner": "user",
      "updatedAt": "2022-04-09T15:00:22.504Z",
    },
  },
  "2022-04-10213a8a85-7a0a-4e97-80c4-36711381f01f": Object {
    "4": Object {
      "PushToken": "ExponentPushToken[*********************]",
      "StackName": "Pasta",
      "StackSchedule": "{\"2 on, 1 off weekdays\":{\"dw\":[0,1,2,3],\"type\":\"DAY_WEEK\",\"startDate\":\"2022-03-13T11:00:00.000Z\"}}",
      "StackScheduleName": "2 on, 1 off weekdays",
      "UserID": "user",
      "createdAt": "2022-04-10T11:00:23.093Z",
      "id": "3fb75293-1afd-4a75-bdfd-b27961cd127e",
      "lastReceipt": "{\"stack\":\"user2022-03-15T15:10:32.642Z<139029b5-4402-4c08-b41f-b7653e64cb93>d2cb5bf8-49ce-4d07-bbdc-aaa9418aae41\",\"ticket\":{\"id\":\"cb888fe2-a925-4571-9bc4-878ee585323b\",\"status\":\"ok\"}}",
      "lastReceiptCreated": "2022-04-10T11:00:23.093Z",
      "owner": "user",
      "updatedAt": "2022-04-10T11:00:23.093Z",
    },
  },
  "2022-04-1122b29a72-c30f-4a33-a164-0497b33122c0": Object {
    "2": Object {
      "PushToken": "ExponentPushToken[*********************]",
      "StackName": "Pasta",
      "StackSchedule": "{\"2 on, 1 off weekdays\":{\"dw\":[0,1,2,3],\"type\":\"DAY_WEEK\",\"startDate\":\"2022-03-13T11:00:00.000Z\"}}",       
      "StackScheduleName": "2 on, 1 off weekdays",
      "UserID": "user",
      "createdAt": "2022-04-11T11:00:22.341Z",
      "id": "ff0d4f69-2d6b-4db8-90f6-db68c642042c",
      "lastReceipt": "{\"stack\":\"user2022-03-15T15:10:32.642Z<139029b5-4402-4c08-b41f-b7653e64cb93>d2cb5bf8-49ce-4d07-bbdc-aaa9418aae41\",\"ticket\":{\"id\":\"dc451865-8b66-4088-93a5-8985d869eb1c\",\"status\":\"ok\"}}",
      "lastReceiptCreated": "2022-04-11T11:00:22.341Z",
      "owner": "user",
      "updatedAt": "2022-04-11T11:00:22.341Z",
    },
  },
  "2022-04-11cc0bdf71-c796-4252-b4aa-9f8ffd7e49a9": Object {
    "3": Object {
      "PushToken": "ExponentPushToken[*********************]",
      "StackName": "Every other skip 4pm",
      "StackSchedule": "{\"Every other day\":{\"skip_days\":2,\"type\":\"SKIP_DAYS\",\"startDate\":\"2022-04-11T19:46:34.080Z\"}}",
      "StackScheduleName": "Every other day",
      "UserID": "user",
      "createdAt": "2022-04-11T20:00:22.549Z",
      "id": "83cf53f2-9981-4eb1-bc8a-28e833de7e67",
      "lastReceipt": "{\"stack\":\"user2022-04-11T19:47:42.994Z<139029b5-4402-4c08-b41f-b7653e64cb93>907e97a3-a438-49e7-bf5f-71ef8b23e7c4\",\"ticket\":{\"id\":\"0a4aa2dd-acb9-4cd2-89f4-5dec0b82337c\",\"status\":\"ok\"}}",
      "lastReceiptCreated": "2022-04-11T20:00:22.549Z",
      "owner": "user",
      "updatedAt": "2022-04-11T20:00:22.549Z",
    },
  },
  "2022-04-11dbf1fc4b-ca38-4ac5-b660-7b10195fc891": Object {
    "1": Object {
      "PushToken": "ExponentPushToken[*********************]",
      "StackName": "Everydaytest",
      "StackSchedule": "{\"Everyday\":{\"dw\":[6,7,1,2,3,4,5],\"type\":\"DAY_WEEK\",\"startDate\":\"2022-04-09T15:00:00.000Z\"}}",
      "StackScheduleName": "Everyday",
      "UserID": "user",
      "createdAt": "2022-04-11T15:00:22.660Z",
      "id": "597233b0-7327-4b13-923c-b3b9073c6747",
      "lastReceipt": "{\"stack\":\"user2022-04-09T14:52:37.519Z<139029b5-4402-4c08-b41f-b7653e64cb93>ba7cb97d-d6db-4086-82dd-b6a8c79712e5\",\"ticket\":{\"id\":\"38eef4db-4914-4885-ae5e-7de509b7d424\",\"status\":\"ok\"}}",
      "lastReceiptCreated": "2022-04-11T15:00:22.660Z",
      "owner": "user",
      "updatedAt": "2022-04-11T15:00:22.660Z",
    },
  },
}

While my markedDates state looks like this:

Object {
  "2022-04-09": Object {
    "dotColor": "grey",
    "marked": true,
  },
  "2022-04-10": Object {
    "dotColor": "grey",
    "marked": true,
  },
  "2022-04-11": Object {
    "dotColor": "grey",
    "marked": true,
  },
}

Up to this point, everything works, but in the following code when I am handling the pressing of the day, and the subsequent opening of the modal, this is where the TypeError: undefined is not an object (evaluating 'item.createdAt') occurs:

CalendarCard.js (second half)

// continuation of above code
...
    function findValueByPrefix(obj,string) {
        let items = [];
        for (let property in obj) {
            if (obj.hasOwnProperty(property) &&
                property.toString().startsWith(string)) {
                    items.push(obj[property]);
                };
        };
        return items;
    };

    const handleDayPress = (day) => {
        const openModalWithData = (dayNotifications) => {
            setDayNotificationItems(dayNotifications);
            showDayModal();            
        };

        const dayString = day.dateString;
        let dayNotifications = findValueByPrefix(notificationItems,dayString);
        openModalWithData(dayNotifications);
    };

    return (
        <Card>
            <Card.Title title='My History'/>
            <Calendar
                onMonthChange={month => handleMonthChange(month)}
                markedDates={markedDates}
                onDayPress={day => handleDayPress(day)}
            />
            <Card.Content/>
        </Card>
    );
};

export default CalendarCard;

In the above code, setDayNotificationItems and showDayModal are passed into CalendarCard from the parent component which can be seen here:

TrackerScreen.js

import React, { useState } from "react";
import { ScrollView, View } from "react-native";
import { Portal } from "react-native-paper";
import { SafeAreaView } from "react-native-safe-area-context";
import CalendarDayModal from "../components/modals/CalendarDayModal";
import CalendarCard from "../components/tracker/CalendarCard";

const TrackerScreen = () => {
    const [dayNotificationItems, setDayNotificationItems] = useState();
    const [dayModalVisible, setDayModalVisible] = useState(false);
    const showDayModal = () => setDayModalVisible(true);
    const hideDayModal = () => setDayModalVisible(false);

    return (
        <SafeAreaView>
            <Portal>
                <CalendarDayModal
                    dayModalVisible={dayModalVisible}
                    hideDayModal={hideDayModal}
                    dayNotificationItems={dayNotificationItems}
                />
            </Portal>
            <ScrollView>
                <View>
                    <CalendarCard
                        showDayModal={showDayModal}
                        setDayNotificationItems={setDayNotificationItems}
                    />
                </View>
            </ScrollView>
        </SafeAreaView>
    );
};

export default TrackerScreen;

From this, the data shown above is passed up from CalendarCard to TrackerScreen when a user presses the day. This then triggers the CalendarDayModal to become visible and render a few components shown here:

CalendarDayModal.js

import React from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import { Caption, Headline, IconButton, Modal } from "react-native-paper";
import DayNotifications from "../tracker/DayNotifications";

const CalendarDayModal = (
    {
        dayModalVisible,
        hideDayModal,
        dayNotificationItems
    }) => {

    const RenderDayNotifications = () => {
        if (dayNotificationItems.length > 0) {
            return (
                <View style={{flexDirection:'column',paddingVertical:5}}>
                    <Headline>Tracked Stacks</Headline>
                    <Caption>The things you tracked this day</Caption>
                    <DayNotifications
                        dayNotificationItems={dayNotificationItems}
                    />
                </View>
            );
        } else return(<View></View>);
    };
    return (
        <Modal
            visible={dayModalVisible}
            onDismiss={hideDayModal}
            contentContainerStyle={{
                backgroundColor:'#D7FFFF',
                width:'100%',
                height: '100%',
                alignSelf:'center'
            }}
        >
            <IconButton
                icon='check'
                size={20}
                style={{paddingTop: 10,position:'absolute',right:0,top:0}}
                onPress={hideDayModal}
            />
            <ScrollView style={{width: '95%',alignSelf:'center'}}>
                <RenderDayNotifications/>
            </ScrollView>
        </Modal>
    );
};

export default CalendarDayModal;

Here we see the end component where the TypeError: undefined is not an object (evaluating 'item.createdAt') is manifesting itself, where the <DayNotifications/> component is attempting to consume the data defined above. However, I believe part of the issue may be with my approach to filtering my data to select the items which match the day object from onDayPress={day => handleDayPress(day)} in the CalendarCard component where I use this function:

    function findValueByPrefix(obj,string) {
        let items = [];
        for (let property in obj) {
            if (obj.hasOwnProperty(property) &&
                property.toString().startsWith(string)) {
                    items.push(obj[property]);
                };
        };

        return items;
    };

    const handleDayPress = (day) => {

        const openModalWithData = (dayNotifications) => {            setDayNotificationItems(dayNotifications);
            showDayModal();            
        };

        const dayString = day.dateString;
        let dayNotifications = findValueByPrefix(notificationItems,dayString);

        openModalWithData(dayNotifications);
    };

Below is my DayNotifications component, where the error is happening:

DayNotifications.js

import React, { useState } from "react";
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import moment from 'moment';
import { View } from "react-native";
import { Caption, Card, Subheading, Text } from "react-native-paper";

const DayNotifications = ({dayNotificationItems}) => {
    const dayItems = [];
    Object.entries(dayNotificationItems).forEach(([k,v]) => dayItems.push(v[`${k}`]));

    const NotificationItemCard = ({item}) => {
        let deliveryDate = moment(item.createdAt).format('dddd, MMMM Do YYYY');
        // ^ ERROR TRIGGERED HERE
        let deliveryTime = moment(item.createdAt).format('h:mma');
        
        return(
            <Card>
                <Card.Title title={deliveryDate} subtitle={deliveryTime}/>
                <View style={{
                    flexDirection: 'column',
                    width: '90%',
                    alignSelf:'center',
                    paddingBottom: 5
                    }}
                >
                    <View style={{
                        flexDirection: 'col',
                        paddingVertical: 10
                        }}
                    >
                        <Subheading>{item.StackName}</Subheading>
                        <Caption>{item.StackScheduleName}</Caption>
                    </View>
                </View>
            </Card>
        );
    };
    return (
        <View style={{
            width: '95%',
            alignContent: 'center',
            alignSelf:'center'
            }}
        >
            {dayItems.map((item) =>
                <NotificationItemCard key={uuidv4()} item={item}/>
            )
            }
        </View>
    );
};

export default DayNotifications;

So, why is it that my calendar is rendering the dots, which the dat is derived from the createdAt field from the object resulting from the query, but after the transformations shown above, my DayNotifications component says that item.createdAt is undefined? Is there an error with the way I'm passing down this data or is the error with the way I am storing the data or more likely is it an issue with the way I'm filtering the data before passing it to DayNotifications (which is what I think but have no idea what the issue is, yet)?

Solved, I was assuming that my findValueByPrefix was going to return consistent keys, but the establishment of the key value in some nested objects (purely for unique key values purposes) was not in fact establishing keys in any order. Hence passing Object.entries(dayNotificationItems).forEach(([k,v]) => dayItems.push(v[ ${k} ])); would try to reference the nested object by a key that was not present in the object (it may have been in some instances just by coincidence, which is why this error was infuriating).

I elected to ignore the keys and just extract the values, since the keys were only there to ensure that objects which may be identical would not be overwritten or de-duped at any step. Here's all I had to do:

...
    const dayItems = [];
    Object.values(dayNotificationItems).forEach(item => {
        Object.values(item).forEach(v => {
            dayItems.push(v)
        });
    });
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM