Search code examples
reactjsreduxredux-sagaseamless-immutablereact-big-calendar

react-big-calendar fetch events from remote url redux issue


I have a weird situation at hand while trying to make a demo appointment system. I want to fetch events through Django backend rest api, which works totally fine and I could see the data coming in console as well.

Here is my index file (where provider is): https://hastebin.com/ubefumofor.js

My calendar rendering component: https://hastebin.com/oronefibap.scala

My Redux, its reduxsauce library I am using: https://hastebin.com/usetavasub.js

My ReduxSaga file: https://hastebin.com/wutuvelefi.js

Now, the thing is, I am struggling to do a few things after I fetch the events:

  • Where to setState, as it goes in infinite look if I call it in componentWillUpdate or componentDidUpdate?

  • if I use this.props.events I get the following error which I have attached as a picture. It says uncaught at fetchEventsByDoctor The sort method cannot be invoked on an Immutable data structure. enter image description here

Now I know that props cant be modified and state could be.

Any suggestions what could be wrong? Thanks

Note: I am new to react and redux related technologies. enter image description here


Solution


componentDidUpdate(prevProps, prevState){

if(prevProps.events !== this.props.events) {

     let a = this.state.events.slice();
     let p = this.props.events
     for(var i = 0; i < p.length; i++ ){
         a[a.length] = p[i]

     }
     this.setState({events: a})



   }
  }

And that should do the trick, but Nagaraj's answer below is very much a good working solution too.


Solution

  • I have changed some things in your js file:

    1. Always use setState function to change the state and do not change the state elements directly, like this.state.events.push(.. is directly updating the state, which is wrong.
    2. Use componentWillReceiveProps to check, if the new props (nextProps) received is different than the old props (this.props), only then setState. Usually, when fetchEvent action is fired, set events to undefined and when you get the response, set it back to the api response in such case you can use:

    Like:

    if (!this.props.events && nextProps.events) {
      this.setState({ events: nextProps.events });
    }
    

    The Calender file:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import BigCalendar from 'react-big-calendar';
    import events from './events';
    import moment from 'moment';
    import {
      ButtonGroup,
      Button,
      Modal,
      Form,
      FieldGroup,
      FormGroup,
      ControlLabel,
      FormControl,
      HelpBlock,
      Alert,
      OverlayTrigger,
      Popover
    } from 'react-bootstrap';
    import DateTime from 'react-datetime';
    import {
      connect
    } from 'react-redux';
    import AppointmentActions from './Redux/AppointmentRedux';
    import EventComponent from './Components/EventComponent';
    import Halogen = require('halogen');
    
    
    BigCalendar.setLocalizer( BigCalendar.momentLocalizer(moment) );
    
    class Calendar extends React.Component {
      props: {
        handleAddCity: () => void,
        bookAppointmentForDoctor: () => void,
        fetchEvents: () => void,
        events: null
      }
    
      constructor(props) {
        super(props);
        this.state = {
          date: new Date(),
          events: props.events,
          fromDate: null,
          toDate: new Date(),
          forValue: 15,
          showAddFormModal: false,
          hours: 12,
          minutes: 20,
          enabled: true,
          showNameError: '',
          showFromDateError: ''
        };
        console.log(this.props)
      }
    
      handleSelect(info) {
        console.log(info.start.toLocaleString())
      }
    
      onClick() {
        // Create a copy of the object before you change the state
        let events = this.state.events.slice();
        events.push({
          'title': 'some Party',
          'start': new Date(2017, 3, 15, 7, 0, 0).toLocaleString(),
          'end': new Date(2017, 3, 16, 10, 30, 0).toLocaleString()
        });
        this.setState({ events });
      }
    
      open() {
        this.setState({ showAddFormModal: true });
      }
    
      close() {
        this.setState({ showAddFormModal: false });
      }
    
      handleFromDateTimeChange(newDate) {
        return this.setState({ fromDate: newDate });
      }
    
      handleToDateTimeChange(newDate) {
        return this.setState({ toDate: newDate });
      }
    
      handleEventSelect(event, _this) {
        return (<Popover id="popover-positioned-right" title="Popover right">
            <strong>Holy guacamole!</strong> Check this info.
          </Popover>);
      }
    
      handleMinutesSelect(_this) {
        this.setState({ forValue: _this.target.value });
      }
    
      handleAddSubmitButton() {
        var flag = true
        const nameValue = ReactDOM.findDOMNode(this.refs.name).value;
        if (nameValue === '') {
          this.setState({ showNameError: "Please input a name. " });
          flag = false;
        }
    
        if (this.state.fromDate === null) {
          this.setState({ showFromDateError: 'Please input right date and time' });
          flag = false;
        }
    
        const fromDate = new Date(this.state.fromDate).toLocaleString();
        const toDate = moment(fromDate).add(this.state.forValue, 'm');
        const newToDate = new Date(toDate).toLocaleString();
    
        console.log(nameValue, this.state.forValue, fromDate, newToDate);
    
        // Create a copy of the object before you change the state
        let events = this.state.events.slice();
        events.push({
          'title': nameValue,
          'start': new Date(fromDate),
          'end': new Date(newToDate),
          'hexColor': '#FF5722',
        });
    
        if (flag) {
          this.props.bookAppointmentForDoctor(3, nameValue, new Date(fromDate), new Date(newToDate))
          this.setState({ showAddFormModal: false });
        }
      }
    
      eventStyleGetter(event, start, end, isSelected) {
        var backgroundColor = event.hexColor;
    
        var style = {
          backgroundColor: backgroundColor,
          borderRadius: '0px',
          opacity: 0.8,
          color: 'black',
          border: '0px',
          display: 'block'
        };
    
        return { style: style };
      }
    
      componentDidMount() {
        this.props.fetchEvents(1);
      }
    
      componentWillReceiveProps(nextProps) {
        let oldEvents = this.props.events;
        let newEvents = nextProps.events;
    
        // Check here if only newEvents is different than oldEvents and update state.
        // Like always set events as undefined if ajax request starts and set it back
        // Once you receive the data
        // if (newEvents <---> oldEvents ??? ) {
          this.setState({ events });
        // }
      }
    
      render() {
        let { hours, minutes, enabled } = this.state;
    
        if (this.props.fetching) {
          return (<h1>loading.....</h1>)
        }
    
        if (this.state.events !== null) {
    
          return (
            <div style={{height: 580}}>
              <h3 className="callout">Book appointment and events here.</h3>
              <BigCalendar
                selectable
                popup
                events={this.state.events}
                height={500}
                defaultView='month'
                scrollToTime={new Date(1970, 1, 1, 6)}
                defaultDate={new Date()}
                onSelectEvent={(event, e) => this.handleEventSelect(event, e)}
                onSelectSlot={(slotInfo) => this.handleSelect(slotInfo)}
                eventPropGetter={(event) => this.eventStyleGetter(event)}
                components={{
                    toolbar: this.CustomToolbar,
                    event: EventComponent,
                  }} 
                />
    
              <Button
                bsStyle="primary"
                bsSize="small"
                onClick={this.open.bind(this)}>
                Add appointment
              </Button>
    
              <Modal show={this.state.showAddFormModal} onHide={this.close.bind(this)}>
                  <Modal.Header closeButton>
                      <Modal.Title>Add Appointment</Modal.Title>
                  </Modal.Header>
                      <Modal.Body>
                        <form>
                             <FormGroup bsSize="large">
                                <FormControl type="text" placeholder="Enter Name" ref="name"/>{this.state.showNameError}
                              </FormGroup>
                           <FormGroup>
                              <ControlLabel>From (Date and Time)</ControlLabel>
                              <DateTime onChange={this.handleFromDateTimeChange.bind(this)}/>
                              { this.state.showFromDateError }
                              <HelpBlock>Please choose Date and Time of from when you want the appointment from,
                                both could be done using the same widget.</HelpBlock>
                            </FormGroup>
                           <FormGroup controlId="formControlsSelect">
                               <ControlLabel>For a period of</ControlLabel>
                               <FormControl componentClass="select" onChange={this.handleMinutesSelect.bind(this)} value={this.state.forValue}>
                                  <option value="15">15 minutes</option>
                                  <option value="30">30 minutes</option>
                                  <option value="45">45 minutes</option>
                                  <option value="60">60 minutes</option>
                                  <option value="90">90 minutes</option>
                                  <option value="120">2 hours</option>
                               </FormControl>
                           </FormGroup>
    
                          <Button type="button" onClick={this.handleAddSubmitButton.bind(this)}>
                              Submit
                          </Button>
                        </form>
                  </Modal.Body>
              </Modal>
            </div>
          )
        }
    
        return (<h1>loading....</h1>);
      }
    }
    
    
    const mapStateToProps = (state) => {
      return {
        city: state.appointment.city,
        events: state.appointment.events,
        fetching: state.appointment.fetching,
      }
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        handleAddCity: (city) => dispatch(AppointmentActions.setUserCity(city)),
        bookAppointmentForDoctor: (doctor_id, customer_name, from_date, to_date) =>
          dispatch(AppointmentActions.bookAppointmentForDoctor(doctor_id, customer_name, from_date, to_date)),
        fetchEvents: (doctor_id) =>
          dispatch(AppointmentActions.fetchEventsByDoctor(doctor_id))
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Calendar)