2

I'm building a React Native gig guide app.

Each gig is represented by a document in the firebase collection and has an associated date property.

I want to display the current day's gigs to the user. I also the user to tap a "next day's gigs" button and be shown a list of gigs for the next day, as follows:

Home page UI

I can get the current day's gigs without issue using the following logic:

  const day = new Date().getDate();
  const month = new Date().getMonth() + 1;
  const year = new Date().getFullYear();
  const dateToday = `${day}/${month}/${year}`;


  //Filtering through gigs to return only current day's gigs
  const gigsToday = gigs.filter((gig) => gig.date === dateToday);

...but how do I show the next/previous day's gigs?

Here's what I've done so far

1.) Convert the new Date() to a UTC+13 time:

  const addHours = (numOfHours, date = new Date()) => {
    date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
    return date;
  };

  let localDate = addHours(13);

2.) Set up an onPress event that increments the state variable daysAdded by 1:

        <Pressable onPress={addDay}>
          <Text style={styles.buttonOptionsText}>next day's gigs</Text>
        </Pressable>

3.) Create a function that adds a day to the localDate variable, and then set that new date to state variable date:

const [date, setDate] = useState(dateToday);

...

  const addDay = () => {
    setDaysAdded(daysAdded + 1);
    localDate.setDate(localDate.getDate() + daysAdded);
    setDate(localDate);
  };

The problem is that the initial date state is not immediately loaded, which means I can't conditionally render dates based on their date. I'm confused about something else - I have a date that I want to manipulate, so do I set this date as a variable or a piece of state?

Anyway, ideally once the user presses the "next days gigs" button, the app will conditionally render the gigs for the next day.

I should also mention that the date being returned from my Firebase Firestore is in the form "DD/MM/YYYY"

Full code is as follows:

GigMap.js

import { useState, useEffect } from "react";
import { StyleSheet, Text, View, Pressable } from "react-native";
import MapView from "react-native-maps";
import { Marker, Callout } from "react-native-maps";
import CalloutView from "./CalloutView";
import { mapStyle } from "../util/mapStyle";
import { useGigs } from "../hooks/useGigs";

const GigMap = ({ navigation }) => {

  const [date, setDate] = useState(dateToday);
  const gigs = useGigs()
  const [daysAdded, setDaysAdded] = useState(1);



  const addHours = (numOfHours, date = new Date()) => {
    date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
    return date;
  };

  let localDate = addHours(13);

  useEffect(() => {
    setDate(localDate);
  }, []);

  const addDay = () => {
    setDaysAdded(daysAdded + 1);
    localDate.setDate(localDate.getDate() + daysAdded);
    setDate(localDate);
  };

  //Generating current date
  const day = new Date().getDate();
  const month = new Date().getMonth() + 1;
  const year = new Date().getFullYear();
  const dateToday = `${day}/${month}/${year}`;


  //Filtering through gigs to return only current day's gigs
  const gigsToday = gigs.filter((gig) => gig.date === dateToday);

  return (
    <View style={styles.container}>
      <Text style={styles.headerText}>Today's gigs</Text>
      <MapView
        initialRegion={{
          latitude: -41.29416,
          longitude: 174.77782,
          latitudeDelta: 0.03,
          longitudeDelta: 0.03,
        }}
        style={styles.map}
        customMapStyle={mapStyle}
      >
        {gigsToday.map((gig, i) => (
          <Marker
            key={i}
            coordinate={{
              latitude: gig.location.latitude,
              longitude: gig.location.longitude,
            }}
            image={require("../assets/Icon_Gold_48x48.png")}
          >
            <Callout
              style={styles.callout}
              onPress={() =>
                navigation.navigate("GigDetails", {
                  venue: gig.venue,
                  date: gig.date,
                  gigName: gig.gigName,
                  time: gig.time,
                })
              }
            >
              <CalloutView
                venue={gig.venue}
                date={gig.date}
                gigName={gig.gigName}
                time={gig.time}
                style={styles.calloutView}
              />
            </Callout>
          </Marker>
        ))}
      </MapView>
      <View style={styles.buttonOptions}>
        <Pressable>
          <Text style={styles.buttonOptionsText}>previous day's gigs</Text>
        </Pressable>
        <Pressable onPress={addDay}>
          <Text style={styles.buttonOptionsText}>next day's gigs</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "column",
    alignItems: "center",
  },
  map: {
    height: 500,
    width: 330,
    margin: 10,
  },
  headerText: {
    color: "black",
    fontSize: 20,
    marginTop: 5,
  },
  callout: {
    width: 200,
    height: 100,
  },
  buttonOptions: {
    flexDirection: "row",
    justifyContent: "flex-start",
  },
  buttonOptionsText: {
    margin: 5,
  },
});

export default GigMap;

Naruto_333
  • 33
  • 3
Josh Simon
  • 159
  • 1
  • 10
  • 27
  • Can I ask why do you add 13 hours? Are the dates in firebase non-utc? Why exactly 13? Will that rule apply to all the clients with different timezones? – Sergey Sosunov Dec 16 '22 at 21:54
  • @SergeySosunov because this is an app specifically for Wellington, New Zealand. Timezone here is UTC+13 – Josh Simon Dec 17 '22 at 03:45
  • Okay, sorry, but I still cant wrap my head around that, you are adding 13 hours to the local date you created on your device, which also has some base UTC offset by default (until your device is not in UTC timezone). So i guess (but i can be wrong) - if you have 22:00 on your clock while you are in UTC+13 timezone, by adding 13 hours you are going to next day and to a completelly different... time. And next question - about "DD/MM/YYYY" in firestore - is that date UTC+13 or UTC0? – Sergey Sosunov Dec 17 '22 at 04:05
  • Additional place to worry about - UTC and GMT difference. NZ is UTC+13, but it has daylights saving time. And `date`'s getDate and etc are returning values with GMT offset, as you can see, not the UTC offset :) So sometimes you will need to add 13, sometimes 12 by your logic. But anyway, please, double check what you are doing, because working with dates is very tricky. – Sergey Sosunov Dec 17 '22 at 04:29

2 Answers2

3

I think you are overcomplicating things. First of all you don't need to create all new individual Date objects just to get the date, month and year. Secondly, you can get the date formatted the way you want by using toLocaleDateString() and specifying the locale for the style of formatting you want. And thirdly, the native JavaScript Date object handles month and year boundaries automatically if you use setDate().

const today = new Date();
const day = today.getDate();
const month = today.getMonth() + 1;
const year = today.getFullYear();

// but you don't even need to do all that
// you can get it formatted for your locale
today.toLocaleDateString('en-NZ'); // '16/12/2022'

// initially this will be today
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

// initially this will be today
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);

// it automatically handles month and year boundaries
const yearEnd = new Date('12/31/2022');
console.log(yearEnd); // Sat Dec 31 2022 00:00:00 GMT-0500 (Eastern Standard Time)

yearEnd.setDate(yearEnd.getDate() + 1);
console.log(yearEnd);  // Sun Jan 01 2023 00:00:00 GMT-0500 (Eastern Standard Time)
Dylan Cristy
  • 916
  • 11
  • 29
  • `toLocaleDateString` - bad idea, it will bring different results for different users with different locales. For my locale it will log `'17.12.2022'`. So the app will not work for me, for example, and people with other (not-yours) locales. – Sergey Sosunov Dec 16 '22 at 22:21
  • Well, you could use `locale` for force the locale to be standardized. And you could use other formatting `options` to force the formatting to be pretty much any way you want. https://stackoverflow.com/a/34015511/988264 – Dylan Cristy Dec 16 '22 at 22:28
  • Exactly, you pointed it now after I asked about that, because i know about that and you know about that. Does the author of the question knows about that and keeps it in mind? I mean that is a nice way to bring a bug in the app that unexperienced developer will really miss. – Sergey Sosunov Dec 16 '22 at 22:31
  • 1
    Ok, updated my answer with OP's locale. – Dylan Cristy Dec 16 '22 at 22:36
2

Your code is not timezones-aware and it has a lot of logic and variables outside of the react state management functions which could cause bugs due to the fact that variables will be recreated and functions will be called on each render. You should use useState and useMemo more frequently. Do not rely on raw variables and functions calls until you clearly know what you are doing.

const {useState, useMemo} = React;

const useGigs = () => {
  return [
    { date: "13/12/2022", someValue: 10 },
    { date: "14/12/2022", someValue: 10 },
    { date: "15/12/2022", someValue: 10 },
    { date: "16/12/2022", someValue: 10 },
    { date: "15/12/2022", someValue: 11 },
    { date: "17/12/2022", someValue: 12 },
    { date: "18/12/2022", someValue: 12 },
    { date: "19/12/2022", someValue: 12 },
    { date: "20/12/2022", someValue: 12 },
    { date: "21/12/2022", someValue: 12 }
  ];
};

function App() {
  const gigs = useGigs();
  // set initial date to Today (now) in milliseconds.
  const [selectedDateMs, setSelectedDateMs] = useState(Date.now());

  // Or this
  const selectedDateString = useMemo(() => {
    const d = new Date(selectedDateMs);
    const day = d.getDate();
    const month = d.getMonth() + 1;
    const year = d.getFullYear();
    return `${day}/${month}/${year}`;
  }, [selectedDateMs]);

  // Or this
  const selectedDateUTCString = useMemo(() => {
    const d = new Date(selectedDateMs);
    const day = d.getUTCDate();
    const month = d.getUTCMonth() + 1;
    const year = d.getUTCFullYear();
    return `${day}/${month}/${year}`;
  }, [selectedDateMs]);

  const filteredGigs = useMemo(() => {
    return gigs.filter((gig) => gig.date === selectedDateString);
  }, [gigs, selectedDateString]);

  const addDays = (amount) => {
    // 1000ms * 60s * 60mins * 24hrs * (amount of day to add or substract)
    setSelectedDateMs((curr) => curr + 1000 * 60 * 60 * 24 * amount);
  };

  return (
    <div className="App">
      <hr />
      <p>Selected date (UTC): {new Date(selectedDateMs).toISOString()}</p>
      <p>Selected date (user local): {new Date(selectedDateMs).toString()}</p>
      <p>Current date (user timezone) string: {selectedDateString}</p>
      <p>Current date (UTC) string: {selectedDateUTCString}</p>
      <hr />
      <p>Gigs for selected day:</p>
      <pre>{JSON.stringify(filteredGigs)}</pre>
      <hr />
      <div>
        <button type="button" onClick={() => addDays(-1)}>
          Prev day
        </button>
        <button type="button" onClick={() => addDays(1)}>
          Next day
        </button>
      </div>
    </div>
  );
}



// v18.x+
ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <App />
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

<div id="root"></div>
Sergey Sosunov
  • 4,124
  • 2
  • 11
  • 15