'React-Native — How to select a start and end date using react-native-calendars (WIX)?

I am trying to enable date range using react-native-calendars. On my app, the calendar loads a few 'markedDates'; now I need to implement a start and end date functionality without affecting these initial dates. Unfortunately, I am struggling to achieve that. Any ideas on how can I do that?

Thank you in advance.

Pseudo-code

  • Load calendar with marked dates
  • Tap on start date
  • Tap on end date
  • Continue

Component

export default class Dates extends Component {
    static navigationOptions = {
        title: 'Choose dates',
    }

    constructor(props) {
        super(props)
        this.state = {
            selected: undefined,
            marked: undefined,
        }
    }

    componentDidMount() {
        this._markDate()
    }

    _markDate = () => {
        nextDay = []

        const marked = {
            [nextDay]: { selected: true, marked: true },
        }

        Util._findShows(resp => {
            resp.map(data => {
                nextDay.push(data.date)
            })
            var obj = nextDay.reduce((c, v) => Object.assign(c, { [v]: { marked: true, dotColor: 'black' } }), {})
            this.setState({ marked: obj })
        })
    }

    _selectDate = obj => {
        this.setState({ selected: obj.dateString })
    }

    render() {
        return (
            <View style={styles.container}>
                <CalendarList
                    // Callback which gets executed when visible months change in scroll view. Default = undefined
                    onVisibleMonthsChange={months => {
                        console.log('now these months are visible', months)
                    }}
                    // Max amount of months allowed to scroll to the past. Default = 50
                    pastScrollRange={0}
                    // Max amount of months allowed to scroll to the future. Default = 50
                    futureScrollRange={12}
                    // Enable or disable scrolling of calendar list
                    scrollEnabled={true}
                    // Enable or disable vertical scroll indicator. Default = false
                    showScrollIndicator={true}
                    markedDates={
                        // [this.state.selected]: { selected: true, disableTouchEvent: true, selectedDotColor: 'orange' },
                        this.state.marked
                    }
                    onDayPress={day => {
                        this._selectDate(day)
                    }}
                />
                <View style={styles.ctaArea}>
                    <TouchableOpacity style={styles.button} onPress={() => this.props.navigation.navigate('Dates')}>
                        <Text style={styles.btTitle}>Continue</Text>
                    </TouchableOpacity>
                </View>
            </View>
        )
    }
}

Render



Solution 1:[1]

I had the same struggle but so I decided to make my own version.

I'm sure it can be done better and have more functionalities but it works okay for me

const [dates, setDates] = useState({});

// get array of all the dates between start and end dates
var getDaysArray = function(start, end) {
    for (var arr=[], dt = new Date(start); dt <= new Date(end); dt.setDate(dt.getDate() + 1)){
        arr.push(useFormatDate(new Date(dt)));
    }
    return arr;
};

// empty object
const emptyObj = (obj: Object) => {
    var props = Object.getOwnPropertyNames(obj);
    for (var i = 0; i < props.length; i++) {
        delete dates[props[i]];
    }
}

// check if first date is smaller or equals to second date
const compareDate = function(first: string, second: string) {
    return (new Date(first) <= new Date(second) ? true : false);
}

// fill with color the date between first and second date selected
const fillRangeDate = function(first: string, second: string) {
    emptyObj(dates);
    let newDates = dates;
    
    newDates[first]={selected: true, Colors[colorScheme].tint};
    newDates[second]={selected: true, color: Colors[colorScheme].tint};
    var range = getDaysArray(first, second);
    for (var i = 1; i < range.length - 1; i++)
        newDates[range[i]]={color: '#70d7c7', textColor: 'white'};
    
    setDates({...newDates})
}

const selectDate = (day) => {
    let selectedDate = day.dateString;
    let newDates = dates;

    // if 2 dates are selected
    if (Object.keys(dates).length >= 2) {
        var props = Object.getOwnPropertyNames(dates);
        if (compareDate(props[0], selectedDate)) {
            fillRangeDate(props[0], selectedDate);
        } else {
            emptyObj(dates);
        }
    } else {
        // 1 date selected
        if (Object.keys(dates).length == 0) {
            newDates[selectedDate]={selected: true, color: Colors[colorScheme].tint};
        } else if (Object.keys(dates).length == 1) { // 2 dates selected
            newDates[selectedDate]={selected: true, color: Colors[colorScheme].tint};
            // if 2nd date < 1st date, cancel range
            var props = Object.getOwnPropertyNames(dates);
            if (compareDate(props[0], props[1])) {
                var range = getDaysArray(props[0], props[1]);
                for (var i = 1; i < range.length - 1; i++) {
                    newDates[range[i]]={color: '#70d7c7', textColor: 'white'};
                }
            } else {
                emptyObj(dates);
            }
        }
    }
    setDates({...newDates})
}

You'll also need to add this function that I implemented as a hook:

const useFormatDate = (date: Date) => {

  function padTo2Digits(num) {
      return num.toString().padStart(2, '0');
  }

  return [
      date.getFullYear(),
      padTo2Digits(date.getMonth() + 1),
      padTo2Digits(date.getDate()),
  ].join('-');
};

Help me to improve this code and maybe create a merge request on the wix/react-native-calendar

Hope this helps

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Retwix