React-native-calendars: How to update markedDates from onDayPress event

Created on 27 Dec 2017  路  11Comments  路  Source: wix/react-native-calendars

Description

So... how to toggle (mark/unmark) dates when onDayPress was called?

Some like this:

Expected Behavior

I tried to do this with Object.keys(), push and splice but markedDates haven't a index to manipulate it, so it's very difficult to me to do this.

Observed Behavior

Date marking

!Disclaimer! Make sure that markedDates param is immutable. If you change markedDates object content but the reference to it does not change calendar update will not be triggered.

Environment

  • yarn info react-native-calendars: {latest: '1.16.1'}

  • yarn info react-native: {latest: '0.51.0'}

  1. Phone/emulator/simulator & version: Genymotion 2.11.0

Reproducible Demo

The same code can to see below:

some imports:

import React, { Component } from 'react'
import moment from 'moment'
import { ScrollView, View, Text } from 'react-native'
import { Calendar } from 'react-native-calendars'

defining constants:

const _format = 'YYYY-MM-DD'
const _today = moment().format(_format)
const _maxDate = moment().add(15, 'days').format(_format)
// It is not possible to reserve some to current day.
let _markedDates = {
    [_today]: {disabled: true}
}

onDayPress method called:

const onDaySelect = (day) => {
    const selectedDay = moment(day.dateString).format(_format)

    const arrDates = Object.keys(_markedDates)
        // .map((value, id, ssss) => ({id, value}))
        .map((d, i) => {

            console.log('d, i: ', _markedDates.indexOf(i))

            // const markedDay = moment(d.value).format(_format)
            // if (_today !== markedDay) {
                // console.log('push: ', markedDay)
            // } else if () {
                // console.log('splice: ', d, i)
            // }
        })

    // console.log('array dates: ', arrDates)
}

and the calendar component below:

const WixCalendar = (props) => {
    return (
        <Calendar
            // theme={{
            //     selectedDayBackgroundColor: 'steelblue',
            //     dotColor: '#00adf5',
            // }}

            // we use moment.js to give the minimum and maximum dates.
            minDate={_today}
            maxDate={_maxDate}

            // hideArrows={true}

            onDayPress={(day) => onDaySelect(day)}
            markedDates={_markedDates}
        />
    )
}

export default WixCalendar

EDIT:

I've been working to improved this with clean JS:

https://codepen.io/francisrod01/pen/RxKQeO

image

I clicked in day 31 but the day doesn't marked in the calendar.

Most helpful comment

You can track the marked dates as state in a stateful React component, here's an example I've based of your expo example:

import React from 'react'
import moment from 'moment' // 2.20.1
import { View } from 'react-native' // 0.0.1
import { Calendar } from 'react-native-calendars' // 1.16.1


const _format = 'YYYY-MM-DD'
const _today = moment().format(_format)
const _maxDate = moment().add(15, 'days').format(_format)

class WixCalendar extends React.Component {
  // It is not possible to select some to current day.
  initialState = {
      [_today]: {disabled: true}
  }

  constructor() {
    super();

    this.state = {
      _markedDates: this.initialState
    }
  }

  onDaySelect = (day) => {
      const _selectedDay = moment(day.dateString).format(_format);

      let marked = true;
      if (this.state._markedDates[_selectedDay]) {
        // Already in marked dates, so reverse current marked state
        marked = !this.state._markedDates[_selectedDay].marked;
      }

      // Create a new object using object property spread since it should be immutable
      // Reading: https://davidwalsh.name/merge-objects
      const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { marked } } }

      // Triggers component to render again, picking up the new state
      this.setState({ _markedDates: updatedMarkedDates });
  }

  render() {
    return (
      <View style={{flex: 1}}>
        <Calendar

            // we use moment.js to give the minimum and maximum dates.
            minDate={_today}
            maxDate={_maxDate}

            // hideArrows={true}

            onDayPress={this.onDaySelect}
            markedDates={this.state._markedDates}
        />
      </View>
    );
  }
}

export default WixCalendar

All 11 comments

You can track the marked dates as state in a stateful React component, here's an example I've based of your expo example:

import React from 'react'
import moment from 'moment' // 2.20.1
import { View } from 'react-native' // 0.0.1
import { Calendar } from 'react-native-calendars' // 1.16.1


const _format = 'YYYY-MM-DD'
const _today = moment().format(_format)
const _maxDate = moment().add(15, 'days').format(_format)

class WixCalendar extends React.Component {
  // It is not possible to select some to current day.
  initialState = {
      [_today]: {disabled: true}
  }

  constructor() {
    super();

    this.state = {
      _markedDates: this.initialState
    }
  }

  onDaySelect = (day) => {
      const _selectedDay = moment(day.dateString).format(_format);

      let marked = true;
      if (this.state._markedDates[_selectedDay]) {
        // Already in marked dates, so reverse current marked state
        marked = !this.state._markedDates[_selectedDay].marked;
      }

      // Create a new object using object property spread since it should be immutable
      // Reading: https://davidwalsh.name/merge-objects
      const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { marked } } }

      // Triggers component to render again, picking up the new state
      this.setState({ _markedDates: updatedMarkedDates });
  }

  render() {
    return (
      <View style={{flex: 1}}>
        <Calendar

            // we use moment.js to give the minimum and maximum dates.
            minDate={_today}
            maxDate={_maxDate}

            // hideArrows={true}

            onDayPress={this.onDaySelect}
            markedDates={this.state._markedDates}
        />
      </View>
    );
  }
}

export default WixCalendar

Thank you @eddiegroves! It's almost that I make here.
The problem now is that the "marked" days doesn't update with style in this marked days.
Is there one way to do that? I think that markedDates is immutable.

Not sure I follow, the example above should be re-rendering and showing the updated marked day automatically. Here's a tweaked example where the dotColor theme is set https://snack.expo.io/r1bMAvN7z

@eddiegroves Thank you for your help from the beginning.
The above example (your second example) doesn't update the marked days.

Well.. I'll from your first example and tell you how it went.

EDIT:

It works for me!! Aleluia!! :1st_place_medal:

image

Thank you for spread operator tips.
I'm still new to ReactJS and I did not remember that technique.

@francisrod01 If you're new to React it will take a while for it to 'click' but keep at it and things like this will be easier! 馃憤

@eddiegroves What we do it better using the new lifecycle's React?

my version without moment, and I had to change two lines to get it to work (on and off):

const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { 'selected': marked } } }

and

marked = !this.state._markedDates[_selectedDay].selected; //from marked = !this.state._markedDates[_selectedDay].marked

all code:

this.state = {
      _markedDates: this.initialState,
     ....
}

showCalendar = () => {
    return (
      <Calendar
        style={{
          borderWidth: 0,
          borderRadius: 4,
        }}
        theme={{
          todayTextColor: '#6de3dc',
          selectedDayBackgroundColor: '#6de3dc',
          selectedDayTextColor: '#ffffff',
        }}
        markingType={'custom'}
        markedDates={this.state._markedDates}
        // Initially visible month. Default = Date()
        // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
        minDate={new Date()}
        // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
        // Handler which gets executed on day press. Default = undefined
        onDayPress={day => this.onDayPress(day)}
        // Handler which gets executed on day long press. Default = undefined
        onDayLongPress={day => {
          console.log('selected day', day)
        }}
        // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting
        monthFormat={'MMM d, yyyy'}
        // Handler which gets executed when visible month changes in calendar. Default = undefined
        onMonthChange={month => {
          console.log('month changed', month)
        }}
        // Hide month navigation arrows. Default = false
        //hideArrows={true}
        // Replace default arrows with custom ones (direction can be 'left' or 'right')
        //renderArrow={(direction) => (<Arrow />)}
        // Do not show days of other months in month page. Default = false
        hideExtraDays={true}
        // If hideArrows=false and hideExtraDays=false do not switch month when tapping on greyed out
        // day from another month that is visible in calendar page. Default = false
        //disableMonthChange={true}
        // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
        firstDay={0}
        // Hide day names. Default = false
        //hideDayNames={true}
        // Show week numbers to the left. Default = false
        //showWeekNumbers={true}
        // Handler which gets executed when press arrow icon left. It receive a callback can go back month
        onPressArrowLeft={substractMonth => substractMonth()}
        // Handler which gets executed when press arrow icon left. It receive a callback can go next month
        onPressArrowRight={addMonth => addMonth()}
      />
    )
  }

initialState = {
      [new Date()]: {disabled: true}
  }

onDayPress = (day) => {
      const _selectedDay = day.dateString;

      let marked = true;
      if (this.state._markedDates[_selectedDay]) {
        // Already in marked dates, so reverse current marked state
        marked = !this.state._markedDates[_selectedDay].selected;
      }

      // Create a new object using object property spread since it should be immutable
      // Reading: https://davidwalsh.name/merge-objects
      const updatedMarkedDates = {...this.state._markedDates, ...{ [_selectedDay]: { 'selected': marked } } }

      // Triggers component to render again, picking up the new state
      this.setState({ _markedDates: updatedMarkedDates });
  }

Does anyone get perf issue while setState?

Not sure I follow, the example above should be re-rendering and showing the updated marked day automatically. Here's a tweaked example where the dotColor theme is set https://snack.expo.io/r1bMAvN7z

Thx so much!!!

Does anyone get perf issue while setState?

yes! how did you handle that?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

moiiiiit picture moiiiiit  路  4Comments

microwin168 picture microwin168  路  4Comments

sommeshEwall picture sommeshEwall  路  3Comments

AleksandrZhukov picture AleksandrZhukov  路  3Comments

nickitatkach picture nickitatkach  路  4Comments