react-big-calendar 从远程 url redux 问题中获取事件
react-big-calendar fetch events from remote url redux issue
我在尝试制作演示预约系统时遇到了一个奇怪的情况。
我想通过 Django 后端 rest api 获取事件,它工作得很好,我也可以在控制台中看到数据。
这是我的索引文件(提供商所在的位置):
https://hastebin.com/ubefumofor.js
我的日历渲染组件:
https://hastebin.com/oronefibap.scala
我的 Redux,我正在使用它的 reduxsauce 库:
https://hastebin.com/usetavasub.js
我的 ReduxSaga 文件:
https://hastebin.com/wutuvelefi.js
现在,问题是,我在获取事件后努力做一些事情:
在哪里设置状态,如果我在 componentWillUpdate 或 componentDidUpdate 中调用它,它会无限看?
如果我使用 this.props.events 我会收到以下错误,我已将其作为图片附上。它说 uncaught at fetchEventsByDoctor The sort method cannot be invoked on an Immutable data structure.
现在我知道 props 不能修改,而 state 可以。
有什么错误的建议吗?谢谢
注:本人刚接触react和redux相关技术
解决方案
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})
}
}
这应该可以解决问题,但下面 Nagaraj 的回答也是一个非常好的解决方案。
我已经更改了你的 js 文件中的一些内容:
- 总是使用setState函数来改变状态,不要直接改变状态元素,像
this.state.events.push(..
是直接更新状态,这是错误的。
- 使用
componentWillReceiveProps
检查,如果收到的新道具(nextProps
)与旧道具(this.props
)不同,则只有setState
。通常,当触发 fetchEvent
操作时,将事件设置为未定义,当您收到响应时,将其设置回 api 响应,在这种情况下您可以使用:
喜欢:
if (!this.props.events && nextProps.events) {
this.setState({ events: nextProps.events });
}
日历文件:
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)
我在尝试制作演示预约系统时遇到了一个奇怪的情况。 我想通过 Django 后端 rest api 获取事件,它工作得很好,我也可以在控制台中看到数据。
这是我的索引文件(提供商所在的位置): https://hastebin.com/ubefumofor.js
我的日历渲染组件: https://hastebin.com/oronefibap.scala
我的 Redux,我正在使用它的 reduxsauce 库: https://hastebin.com/usetavasub.js
我的 ReduxSaga 文件: https://hastebin.com/wutuvelefi.js
现在,问题是,我在获取事件后努力做一些事情:
在哪里设置状态,如果我在 componentWillUpdate 或 componentDidUpdate 中调用它,它会无限看?
如果我使用 this.props.events 我会收到以下错误,我已将其作为图片附上。它说
uncaught at fetchEventsByDoctor The sort method cannot be invoked on an Immutable data structure.
现在我知道 props 不能修改,而 state 可以。
有什么错误的建议吗?谢谢
注:本人刚接触react和redux相关技术
解决方案
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})
}
}
这应该可以解决问题,但下面 Nagaraj 的回答也是一个非常好的解决方案。
我已经更改了你的 js 文件中的一些内容:
- 总是使用setState函数来改变状态,不要直接改变状态元素,像
this.state.events.push(..
是直接更新状态,这是错误的。 - 使用
componentWillReceiveProps
检查,如果收到的新道具(nextProps
)与旧道具(this.props
)不同,则只有setState
。通常,当触发fetchEvent
操作时,将事件设置为未定义,当您收到响应时,将其设置回 api 响应,在这种情况下您可以使用:
喜欢:
if (!this.props.events && nextProps.events) {
this.setState({ events: nextProps.events });
}
日历文件:
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)