简体   繁体   中英

React Native - rerender child component from parent component

I am trying to modify a calendar - https://github.com/wix/react-native-calendars so that when I press a day, the background and text colors change for that day. I read that to re-render a component you need to update the state of the parent component, so that is what I am trying:

Parent.js

export default class InitiateRent extends Component {
  state = {
    ....
    this.markedDays: {},
    someVar: 0
  };

  constructor() {
    super(); 
    this.handler = this.handler.bind(this);
    this.markedDays = {};
  }

  ....

  handler(e) {
    console.log('handler running');
    this.setState({
      someVar: 123
    })
  }

  <Calendar 
    handler={this.handler}
    markedDates={this.markedDays}
    onDayPress={(day)=> {
      console.log('selected day', day);

      var dateString = `'${day.year}-${day.month}-${day.day}'`

      var addDate = {
        [dateString]: {
          customStyles: {
            container: {
              backgroundColor: '#6de3dc',
            },
            text: {
              color: 'white',
              fontWeight: 'bold'
            },
          },
        }
      }

      this.markedDays = Object.assign(addDate, this.markedDays);

      console.log(this.markedDays);
    }}

....

Child.js

pressDay(date) {
    this.props.handler();
    this._handleDayInteraction(date, this.props.onDayPress);
}

renderDay(day, id) { //GETS CALLED IN CHILD RENDER METHOD VIA AN INTERMEDIARY
    ....
        <DayComp
          ....
          onPress={this.pressDay}
          ....
}

Everything functions correctly except I don't get the expected result - when I press a date on the calendar, the handler fires, the state changes, and in the console my object looks correct:

Object

'2018-9-23': {customStyles: {container: {backgroundColor: "#6de3dc"}, text: {color: "white", fontWeight: "bold"}}}

'2018-9-26': {customStyles: {container: {backgroundColor: "#6de3dc"}, text: {color: "white", fontWeight: "bold"}}}

'2018-9-28': {customStyles: {container: {backgroundColor: "#6de3dc"}, text: {color: "white", fontWeight: "bold"}}}

'2018-9-29': {customStyles: {container: {backgroundColor: "#6de3dc"}, text: {color: "white", fontWeight: "bold"}}}

But the background of the day on the calendar doesn't change, neither does the text - what do I have to do to get it to re-render (change)?

UPDATE

I created a chain of handlers all the way from the very bottom day component which represents just a day on the calendar all the way up to the highest order parent view (not App.js, but just below it) and that still did nothing.

UPDATE

The calendar is on a modal, and I don't have any handler on that updating its state, could that be the problem?

UPDATE

I found this in the docs:

.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.

What does this mean?

UPDATE

I tried to interpret what that meant with some google searches, does this make this.state.markedDays immutable?:

                const markedDays = this.state.markedDays;

                var dateString = `'${day.year}-${day.month}-${day.day}'`;

                var addDate = {
                  [dateString]: {
                    customStyles: {
                      container: {
                        backgroundColor: '#6de3dc',
                      },
                      text: {
                        color: 'white',
                        fontWeight: 'bold'
                      },
                    },
                  }
                }

                const markedDaysHolder = {
                  ...markedDays,
                  ...addDate
                }
                
                this.state.markedDays = markedDaysHolder;

UPDATE

I put:

componentDidUpdate(prevProps, prevState, snapshot) {
  console.log("componentDidUpdate in InitiateRent:", prevProps, prevState, snapshot);
}

The output of the above is:

componentDidUpdate in InitiateRent: (3) (index.bundle, line 761)

{initiateRentMessage: function, modalHeight: 200, modalWidth: 200, handler: function}

{modalVisible: true, message: "Hi, I would like to rent an item from you.", rentButtonBackground: "#6de3dc", someVar: 123, markedDays: Object}

undefined

I can see the state object markedDays getting bigger with each button press from this output, why isn't the style changing?

In all the relevant components, and I noticed it doesn't fire on the lowest order component, which is the one that needs to change.

I also had this problem, this is what you need to do

constructor(props) {
    super(props)
    this.state = {
    }
    this.onDayPress = this.onDayPress
  }

showCalendar = () => {
    return (
      <Calendar
        onDayPress={this.onDayPress}
        style={styles.calendar}
        hideExtraDays
        markedDates={{
          [this.state.selected]: {
            selected: true,
            disableTouchEvent: true,
            selectedDotColor: 'orange',
          },
        }}
      />
    )
  }

onDayPress = day => {
    this.setState({
      selected: day.dateString,
    })
  }

This is the flushed out answer for what I was trying to do. I wanted to be able to select and deselect a date by pressing on it (deselecting being the issue after @HaiderAli helped):

All I had to do was:

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;
    console.log('marked:', 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]: {
        'selected': marked,
        customStyles: {
          container: {
            backgroundColor: '#6de3dc',
          },
          text: {
            color: 'white',
            fontWeight: 'bold'
          },
        },
      }
    }
  }

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

I added 'selected': , which needs to be there and this function improves @HaiderAli's answer.

To get the deselecting to work, open node_modules/react-native-calendars/src/calendar/day/custom/index.js (This is the wrong file for you if you aren't using markingType={'custom'} prop on the calendar. If you aren't using the prop, edit node_modules/react-native-calendars/src/calendar/day/basic/index.js ). Make this change:

....

render() {
    let containerStyle = [this.style.base];
    let textStyle = [this.style.text];

    let marking = this.props.marking || {};
    if (marking && marking.constructor === Array && marking.length) {
      marking = {
        marking: true
      };
    }
    const isDisabled = typeof marking.disabled !== 'undefined' ? marking.disabled : this.props.state === 'disabled';

    console.log('marking in day:', marking.selected);

    if (marking.selected) {
      containerStyle.push(this.style.selected);
    } else if (isDisabled) {
      textStyle.push(this.style.disabledText);
    } else if (this.props.state === 'today') {
      containerStyle.push(this.style.today);
      textStyle.push(this.style.todayText);

    /********ADD THIS CONDITION********/
    } else if(!marking.selected) { 
      textStyle.push({backgroundColor: '#ffffff', color: '#2d4150'});
    }

....
import { View, Text, StyleSheet } from 'react-native';
import React, { useState, useEffect } from 'react';
import { Calendar, CalendarList, Agenda } from 'react-native-calendars';

const Calender = () => {
  const [selectedDay, setSelectedDay] = useState();

  return (
    <View style={{ flex: 1, backgroundColor: 'white' }}>
      <Calendar
        style={[styles.calendar, { height: 350 }]}
        onDayPress={(day) => {
          setSelectedDay({ selected: day.dateString });
        }}
        enableSwipeMonths={true}
        markedDates={{
          [selectedDay?.selected]: {
            selected: true,
            disableTouchEvent: true,
            selectedDotColor: 'orange',
          },
        }}
      />
    </View>
  );
};

export default Calender;

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