Edit Form in React Calendar - javascript

I have a calendar builded with React and Redux. By clicking on any empty date, the modal appears that allows to add some event.
The problem is I can't figure out how to edit that event afterwards by clicking on it. What is the right algorithms?
Here is the calendar page code
/* eslint-disable no-unused-vars */
import React, { PropTypes } from 'react';
import moment from 'moment-timezone';
import Helmet from 'react-helmet';
import _ from 'lodash';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { GoogleLogin, GoogleLogout } from 'react-google-login';
import { reduxForm, reset } from 'redux-form';
import BigCalendar from 'react-big-calendar';
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
import 'react-big-calendar/lib/less/styles.less';
import 'react-big-calendar/lib/addons/dragAndDrop/styles.less';
import AddCalendarEventForm from '../../../app/components/AddCalendarEventForm';
import { translate } from '../../../common/utilities/localization';
import {
selectCurrentUser,
selectCurrentGoogleUser,
} from '../../containers/App/selectors';
import {
submitGoogleAuth,
fetchGoogleCalendarEvents,
editGoogleCalendarEvent,
addGoogleCalendarEvent,
} from './actions';
import {
selectGoogleAuth,
selectCalendarEvents,
selectAddEventProcess,
} from './selectors';
const formName = 'addCalendarEvent';
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
const localizer = BigCalendar.momentLocalizer(moment);
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser(),
currentGoogleUser: selectCurrentGoogleUser(),
googleAuth: selectGoogleAuth(),
calendarEvents: selectCalendarEvents(),
addEventProcess: selectAddEventProcess(),
});
const mapDispatchToProps = (dispatch) => ({
submitGoogleAuth: (externalUserId, googleToken) => dispatch(submitGoogleAuth(externalUserId, googleToken)),
fetchGoogleCalendarEvents: (data) => dispatch(fetchGoogleCalendarEvents(data)),
editGoogleCalendarEvent: (data) => dispatch(editGoogleCalendarEvent(data)),
addGoogleCalendarEvent: (data) => dispatch(addGoogleCalendarEvent(data)),
resetForm: () => dispatch(reset(formName)),
});
#reduxForm({
form: formName,
})
#connect(mapStateToProps, mapDispatchToProps)
export default class CalendarPage extends React.Component {
static propTypes = {
currentUser: PropTypes.any,
currentGoogleUser: PropTypes.any,
submitGoogleAuth: PropTypes.func.isRequired,
googleAuth: PropTypes.object,
fetchGoogleCalendarEvents: PropTypes.func,
calendarEvents: PropTypes.object,
editGoogleCalendarEvent: PropTypes.func,
addGoogleCalendarEvent: PropTypes.func,
addEventProcess: PropTypes.object,
resetForm: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
events: [],
show: null,
calendarEvent: null,
};
this.onSuccess = this.onSuccess.bind(this);
this.onFailure = this.onFailure.bind(this);
this.moveEvent = this.moveEvent.bind(this);
this.newEvent = this.newEvent.bind(this);
this.showEventModal = this.showEventModal.bind(this);
this.hideEventModal = this.hideEventModal.bind(this);
}
componentDidMount() {
const { currentUser, currentGoogleUser } = this.props;
if (currentGoogleUser && currentGoogleUser.expires_at && moment(currentGoogleUser.expires_at).isAfter(moment())) {
this.props.fetchGoogleCalendarEvents({ ...currentGoogleUser, userId: currentUser.id });
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentGoogleUser !== this.props.currentGoogleUser) {
this.props.fetchGoogleCalendarEvents({ ...nextProps.currentGoogleUser, userId: nextProps.currentUser.id });
}
if (nextProps.calendarEvents && nextProps.calendarEvents.details) {
const events = [];
for (const item of nextProps.calendarEvents.details.items) {
if (item.start && item.end) {
events.push({
id: item.id,
title: item.summary,
start: moment(item.start.dateTime || item.start.date),
end: moment(item.end.dateTime || item.end.date),
});
}
}
this.setState({ events });
}
if (!nextProps.addEventProcess.isSubmitting && this.props.addEventProcess.isSubmitting) {
this.hideEventModal();
}
}
onSuccess(ev) {
const { submitGoogleAuth, currentUser } = this.props;
submitGoogleAuth(currentUser.id, { ...ev.tokenObj, profileEmail: ev.profileObj.email });
}
onFailure(ev) {
console.log('onFailure', ev);
}
moveEvent({ event, start, end, isAllDay: droppedOnAllDaySlot }) {
const { currentUser, editGoogleCalendarEvent, calendarEvents } = this.props;
const { events } = this.state;
let onlyDate = false;
const idx = events.indexOf(event);
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
let allDay = event.allDay;
if (!event.allDay && droppedOnAllDaySlot) {
allDay = true;
} else if (event.allDay && !droppedOnAllDaySlot) {
allDay = false;
}
const updatedEvent = { ...event, start, end, allDay };
const nextEvents = [...events];
nextEvents.splice(idx, 1, updatedEvent);
if (eventIndex !== -1) {
const item = calendarEvents.details.items[eventIndex];
if (item.start.date && item.end.date) {
updatedEvent.start = moment(start).format('YYYY-MM-DD');
updatedEvent.end = moment(end).format('YYYY-MM-DD');
onlyDate = true;
}
}
this.setState({
events: nextEvents,
}, () => {
editGoogleCalendarEvent({ ...updatedEvent, userId: currentUser.id, timezone: currentUser.timezone, onlyDate });
});
}
resizeEvent = ({ event, start, end }) => {
const { events } = this.state;
const nextEvents = events.map(existingEvent => {
return existingEvent.id === event.id
? { ...existingEvent, start, end }
: existingEvent;
});
this.setState({
events: nextEvents,
});
// console.log(`${event.title} was resized to ${start}-${end}`);
}
newEvent(params) {
const { currentUser, addGoogleCalendarEvent } = this.props;
const { event, formValues } = params;
const newEvent = {
title: formValues.title,
description: formValues.description ? formValues.description : null,
allDay: event.slots.length === 1,
start: moment(event.start).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
end: moment(event.end).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
};
this.setState({
calendarEvent: null,
}, () => {
addGoogleCalendarEvent({ ...newEvent, userId: currentUser.id, timezone: currentUser.timezone });
});
}
showEventModal(event) {
this.setState({ calendarEvent: event, show: true });
}
hideEventModal() {
const { resetForm } = this.props;
this.setState({ show: false, calendarEvent: null }, () => {
resetForm();
});
}
render() {
const { currentGoogleUser, addEventProcess } = this.props;
let authorized = false;
if (currentGoogleUser && currentGoogleUser.expires_at) {
authorized = moment(currentGoogleUser.expires_at).isAfter(moment());
}
return (
<div>
<div className="container-fluid">
<Helmet title={translate('portals.page.calendarPage.helmetTitle')} />
<section className="calendar-section">
<h2 className="main-heading">{translate('portals.page.calendarPage.pageTitle')}</h2>
{!authorized &&
<GoogleLogin
clientId={GOOGLE_CLIENT_ID}
scope="https://www.googleapis.com/auth/calendar"
className="google-login"
onSuccess={this.onSuccess}
onFailure={this.onFailure}
>
<i className="google-image" />
<span> Sign in with Google</span>
</GoogleLogin>
}
{authorized &&
<DragAndDropCalendar
selectable
events={this.state.events}
localizer={localizer}
onEventDrop={this.moveEvent}
resizable
onEventResize={this.resizeEvent}
onSelectSlot={this.showEventModal}
onSelectEvent={(e) => { console.log('e', e); }}
defaultView={BigCalendar.Views.MONTH}
defaultDate={new Date()}
views={{ month: true }}
/>
}
<AddCalendarEventForm
show={this.state.show}
isSubmitting={addEventProcess.isSubmitting}
calendarEvent={this.state.calendarEvent}
onSubmit={this.newEvent}
onHide={this.hideEventModal}
/>
</section>
</div>
</div>
);
}
}
And here is AddEventForm code
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { Field, touch, reduxForm, reset } from 'redux-form';
import { Modal } from 'react-bootstrap';
import _ from 'lodash';
import Input from '../../../common/components/Input';
import ReactSelect from '../../../common/components/Input/ReactSelect';
import CenteredModal from '../../../common/components/CenteredModal/index';
import LoadingSpinner from '../../components/LoadingSpinner';
import { selectCurrentUser } from '../../containers/App/selectors';
import { selectSyncErrorBool, selectSyncErrors, selectValues } from '../../common/selectors/form.selector';
import validator, { fields } from './validator';
const formName = 'addCalendarEvent';
function numberSequenceCreator(start, end) {
return _.range(start, end).map(n => {
if (n < 10) {
return {
label: `0${n}`,
value: n.toString(),
};
}
return {
label: n.toString(),
value: n.toString(),
};
});
}
const hourOptions = numberSequenceCreator(1, 13);
const minuteOptions = numberSequenceCreator(0, 60);
const periodOptions = [
{ label: 'AM', value: 'AM' },
{ label: 'PM', value: 'PM' },
];
const mapDispatchToProps = (dispatch) => ({
touchFields: () => dispatch(touch(formName, ...fields)),
resetForm: () => dispatch(reset(formName)),
});
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser(),
formError: selectSyncErrorBool(formName),
formErrors: selectSyncErrors(formName),
formValues: selectValues(formName),
});
#reduxForm({
form: formName,
validate: validator,
enableReinitialize: true,
keepDirtyOnReinitialize: false,
})
#connect(mapStateToProps, mapDispatchToProps)
export default class AddCalendarEventForm extends Component { // eslint-disable-line react/prefer-stateless-function
static propTypes = {
resetForm: PropTypes.func,
currentUser: PropTypes.object,
formError: PropTypes.bool,
formErrors: PropTypes.object,
formValues: PropTypes.object,
show: PropTypes.bool,
onHide: PropTypes.func,
onSubmit: PropTypes.func,
touchFields: React.PropTypes.func.isRequired,
calendarEvent: PropTypes.object,
isSubmitting: PropTypes.bool,
};
constructor(props) {
super(props);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleCloseModal() {
this.props.resetForm();
this.props.onHide(false);
}
handleFormSubmit(event) {
event.preventDefault();
const { formError, formValues, onSubmit, calendarEvent, touchFields } = this.props;
if (!formValues.title || !formValues.period || !formValues.hour || !formValues.minute) {
touchFields();
} else if (!formError) {
onSubmit({ event: calendarEvent, formValues });
}
}
render() {
const { show, isSubmitting } = this.props;
return (
<div>
<Modal
className="edit-study-modal"
id="edit-study"
dialogComponentClass={CenteredModal}
show={show}
onHide={this.handleCloseModal}
backdrop
keyboard
>
<Modal.Header>
<Modal.Title>Add Calendar Event</Modal.Title>
<a className="lightbox-close close" onClick={this.handleCloseModal}>
<i className="icomoon-icon_close" />
</a>
</Modal.Header>
<Modal.Body>
<div className="form">
<div className="inner">
<form className="" onSubmit={this.handleFormSubmit}>
<div className="form-lightbox">
<div className="clearfix">
<div className="field-row">
<strong className="required label">
<label>Title</label>
</strong>
<div className="field">
<Field
name="title"
component={Input}
type="text"
/>
</div>
</div>
<div className="field-row time-field-row">
<strong className="label required">
<label>Time</label>
</strong>
<div className="field time-field">
<div className="row">
<div className="col-small pull-left hours">
<Field
name="hour"
placeholder="Hours"
options={hourOptions}
component={ReactSelect}
/>
</div>
<div className="col-small pull-left minutes">
<Field
name="minute"
placeholder="Minutes"
options={minuteOptions}
component={ReactSelect}
/>
</div>
<div className="col-small pull-left time-mode">
<Field
name="period"
placeholder="AM/PM"
options={periodOptions}
component={ReactSelect}
/>
</div>
</div>
</div>
</div>
<div className="field-row">
<strong className="label">
<label>Description</label>
</strong>
<Field
name="description"
component={Input}
className="field"
bsClass="form-control input-lg"
componentClass="textarea"
/>
</div>
<button
type="submit"
className="btn btn-default btn-submit pull-right"
>
{isSubmitting
? <span><LoadingSpinner showOnlyIcon size={20} /></span>
: <span>Submit</span>
}
</button>
</div>
</div>
</form>
</div>
</div>
</Modal.Body>
</Modal>
</div>
);
}
}

Here is the calendar page code
/* eslint-disable no-unused-vars */
import React, { PropTypes } from 'react';
import moment from 'moment-timezone';
import Helmet from 'react-helmet';
import _ from 'lodash';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { GoogleLogin, GoogleLogout } from 'react-google-login';
import { reduxForm, reset } from 'redux-form';
import BigCalendar from 'react-big-calendar';
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
import 'react-big-calendar/lib/less/styles.less';
import 'react-big-calendar/lib/addons/dragAndDrop/styles.less';
import AddCalendarEventForm from '../../../app/components/AddCalendarEventForm';
import { translate } from '../../../common/utilities/localization';
import {
selectCurrentUser,
selectCurrentGoogleUser,
} from '../../containers/App/selectors';
import {
submitGoogleAuth,
fetchGoogleCalendarEvents,
editGoogleCalendarEvent,
addGoogleCalendarEvent,
} from './actions';
import {
selectGoogleAuth,
selectCalendarEvents,
selectAddEventProcess,
} from './selectors';
const formName = 'addCalendarEvent';
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
const localizer = BigCalendar.momentLocalizer(moment);
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser(),
currentGoogleUser: selectCurrentGoogleUser(),
googleAuth: selectGoogleAuth(),
calendarEvents: selectCalendarEvents(),
addEventProcess: selectAddEventProcess(),
});
const mapDispatchToProps = (dispatch) => ({
submitGoogleAuth: (externalUserId, googleToken) => dispatch(submitGoogleAuth(externalUserId, googleToken)),
fetchGoogleCalendarEvents: (data) => dispatch(fetchGoogleCalendarEvents(data)),
editGoogleCalendarEvent: (data) => dispatch(editGoogleCalendarEvent(data)),
addGoogleCalendarEvent: (data) => dispatch(addGoogleCalendarEvent(data)),
resetForm: () => dispatch(reset(formName)),
});
#reduxForm({
form: formName,
})
#connect(mapStateToProps, mapDispatchToProps)
export default class CalendarPage extends React.Component {
static propTypes = {
currentUser: PropTypes.any,
currentGoogleUser: PropTypes.any,
submitGoogleAuth: PropTypes.func.isRequired,
googleAuth: PropTypes.object,
fetchGoogleCalendarEvents: PropTypes.func,
calendarEvents: PropTypes.object,
editGoogleCalendarEvent: PropTypes.func,
addGoogleCalendarEvent: PropTypes.func,
addEventProcess: PropTypes.object,
resetForm: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
events: [],
show: null,
calendarEvent: null,
};
this.onSuccess = this.onSuccess.bind(this);
this.onFailure = this.onFailure.bind(this);
this.moveEvent = this.moveEvent.bind(this);
this.newEvent = this.newEvent.bind(this);
this.showEventModal = this.showEventModal.bind(this);
this.hideEventModal = this.hideEventModal.bind(this);
}
componentDidMount() {
const { currentUser, currentGoogleUser } = this.props;
if (currentGoogleUser && currentGoogleUser.expires_at && moment(currentGoogleUser.expires_at).isAfter(moment())) {
this.props.fetchGoogleCalendarEvents({ ...currentGoogleUser, userId: currentUser.id });
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentGoogleUser !== this.props.currentGoogleUser) {
this.props.fetchGoogleCalendarEvents({ ...nextProps.currentGoogleUser, userId: nextProps.currentUser.id });
}
if (nextProps.calendarEvents && nextProps.calendarEvents.details) {
const events = [];
for (const item of nextProps.calendarEvents.details.items) {
if (item.start && item.end) {
events.push({
id: item.id,
title: item.summary,
start: moment(item.start.dateTime || item.start.date),
end: moment(item.end.dateTime || item.end.date),
});
}
}
this.setState({ events });
}
if (!nextProps.addEventProcess.isSubmitting && this.props.addEventProcess.isSubmitting) {
this.hideEventModal();
}
}
onSuccess(ev) {
const { submitGoogleAuth, currentUser } = this.props;
submitGoogleAuth(currentUser.id, { ...ev.tokenObj, profileEmail: ev.profileObj.email });
}
onFailure(ev) {
console.log('onFailure', ev);
}
moveEvent({ event, start, end, isAllDay: droppedOnAllDaySlot }) {
const { currentUser, editGoogleCalendarEvent, calendarEvents } = this.props;
const { events } = this.state;
let onlyDate = false;
const idx = events.indexOf(event);
const eventIndex = _.findIndex(calendarEvents.details.items, { id: event.id });
let allDay = event.allDay;
if (!event.allDay && droppedOnAllDaySlot) {
allDay = true;
} else if (event.allDay && !droppedOnAllDaySlot) {
allDay = false;
}
const updatedEvent = { ...event, start, end, allDay };
const nextEvents = [...events];
nextEvents.splice(idx, 1, updatedEvent);
if (eventIndex !== -1) {
const item = calendarEvents.details.items[eventIndex];
if (item.start.date && item.end.date) {
updatedEvent.start = moment(start).format('YYYY-MM-DD');
updatedEvent.end = moment(end).format('YYYY-MM-DD');
onlyDate = true;
}
}
this.setState({
events: nextEvents,
}, () => {
editGoogleCalendarEvent({ ...updatedEvent, userId: currentUser.id, timezone: currentUser.timezone, onlyDate });
});
}
resizeEvent = ({ event, start, end }) => {
const { events } = this.state;
const nextEvents = events.map(existingEvent => {
return existingEvent.id === event.id
? { ...existingEvent, start, end }
: existingEvent;
});
this.setState({
events: nextEvents,
});
// console.log(`${event.title} was resized to ${start}-${end}`);
}
newEvent(params) {
const { currentUser, addGoogleCalendarEvent } = this.props;
const { event, formValues } = params;
const newEvent = {
title: formValues.title,
description: formValues.description ? formValues.description : null,
allDay: event.slots.length === 1,
start: moment(event.start).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
end: moment(event.end).hours(formValues.period === 'AM' ? formValues.hour % 12 : (formValues.hour % 12) + 12).minutes(formValues.minute).toISOString(),
};
this.setState({
calendarEvent: null,
}, () => {
addGoogleCalendarEvent({ ...newEvent, userId: currentUser.id, timezone: currentUser.timezone });
});
}
showEventModal(event) {
this.setState({ calendarEvent: event, show: true });
}
hideEventModal() {
const { resetForm } = this.props;
this.setState({ show: false, calendarEvent: null }, () => {
resetForm();
});
}
render() {
const { currentGoogleUser, addEventProcess } = this.props;
let authorized = false;
if (currentGoogleUser && currentGoogleUser.expires_at) {
authorized = moment(currentGoogleUser.expires_at).isAfter(moment());
}
return (
<div>
<div className="container-fluid">
<Helmet title={translate('portals.page.calendarPage.helmetTitle')} />
<section className="calendar-section">
<h2 className="main-heading">{translate('portals.page.calendarPage.pageTitle')}</h2>
{!authorized &&
<GoogleLogin
clientId={GOOGLE_CLIENT_ID}
scope="https://www.googleapis.com/auth/calendar"
className="google-login"
onSuccess={this.onSuccess}
onFailure={this.onFailure}
>
<i className="google-image" />
<span> Sign in with Google</span>
</GoogleLogin>
}
{authorized &&
<DragAndDropCalendar
selectable
events={this.state.events}
localizer={localizer}
onEventDrop={this.moveEvent}
resizable
onEventResize={this.resizeEvent}
onSelectSlot={this.showEventModal}
onSelectEvent={(e) => { console.log('e', e); }}
defaultView={BigCalendar.Views.MONTH}
defaultDate={new Date()}
views={{ month: true }}
/>
}
<AddCalendarEventForm
show={this.state.show}
isSubmitting={addEventProcess.isSubmitting}
calendarEvent={this.state.calendarEvent}
onSubmit={this.newEvent}
onHide={this.hideEventModal}
/>
</section>
</div>
</div>
);
}
}
And here is AddEventForm code
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { Field, touch, reduxForm, reset } from 'redux-form';
import { Modal } from 'react-bootstrap';
import _ from 'lodash';
import Input from '../../../common/components/Input';
import ReactSelect from '../../../common/components/Input/ReactSelect';
import CenteredModal from '../../../common/components/CenteredModal/index';
import LoadingSpinner from '../../components/LoadingSpinner';
import { selectCurrentUser } from '../../containers/App/selectors';
import { selectSyncErrorBool, selectSyncErrors, selectValues } from '../../common/selectors/form.selector';
import validator, { fields } from './validator';
const formName = 'addCalendarEvent';
function numberSequenceCreator(start, end) {
return _.range(start, end).map(n => {
if (n < 10) {
return {
label: `0${n}`,
value: n.toString(),
};
}
return {
label: n.toString(),
value: n.toString(),
};
});
}
const hourOptions = numberSequenceCreator(1, 13);
const minuteOptions = numberSequenceCreator(0, 60);
const periodOptions = [
{ label: 'AM', value: 'AM' },
{ label: 'PM', value: 'PM' },
];
const mapDispatchToProps = (dispatch) => ({
touchFields: () => dispatch(touch(formName, ...fields)),
resetForm: () => dispatch(reset(formName)),
});
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser(),
formError: selectSyncErrorBool(formName),
formErrors: selectSyncErrors(formName),
formValues: selectValues(formName),
});
#reduxForm({
form: formName,
validate: validator,
enableReinitialize: true,
keepDirtyOnReinitialize: false,
})
#connect(mapStateToProps, mapDispatchToProps)
export default class AddCalendarEventForm extends Component { // eslint-disable-line react/prefer-stateless-function
static propTypes = {
resetForm: PropTypes.func,
currentUser: PropTypes.object,
formError: PropTypes.bool,
formErrors: PropTypes.object,
formValues: PropTypes.object,
show: PropTypes.bool,
onHide: PropTypes.func,
onSubmit: PropTypes.func,
touchFields: React.PropTypes.func.isRequired,
calendarEvent: PropTypes.object,
isSubmitting: PropTypes.bool,
};
constructor(props) {
super(props);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleCloseModal() {
this.props.resetForm();
this.props.onHide(false);
}
handleFormSubmit(event) {
event.preventDefault();
const { formError, formValues, onSubmit, calendarEvent, touchFields } = this.props;
if (!formValues.title || !formValues.period || !formValues.hour || !formValues.minute) {
touchFields();
} else if (!formError) {
onSubmit({ event: calendarEvent, formValues });
}
}
render() {
const { show, isSubmitting } = this.props;
return (
<div>
<Modal
className="edit-study-modal"
id="edit-study"
dialogComponentClass={CenteredModal}
show={show}
onHide={this.handleCloseModal}
backdrop
keyboard
>
<Modal.Header>
<Modal.Title>Add Calendar Event</Modal.Title>
<a className="lightbox-close close" onClick={this.handleCloseModal}>
<i className="icomoon-icon_close" />
</a>
</Modal.Header>
<Modal.Body>
<div className="form">
<div className="inner">
<form className="" onSubmit={this.handleFormSubmit}>
<div className="form-lightbox">
<div className="clearfix">
<div className="field-row">
<strong className="required label">
<label>Title</label>
</strong>
<div className="field">
<Field
name="title"
component={Input}
type="text"
/>
</div>
</div>
<div className="field-row time-field-row">
<strong className="label required">
<label>Time</label>
</strong>
<div className="field time-field">
<div className="row">
<div className="col-small pull-left hours">
<Field
name="hour"
placeholder="Hours"
options={hourOptions}
component={ReactSelect}
/>
</div>
<div className="col-small pull-left minutes">
<Field
name="minute"
placeholder="Minutes"
options={minuteOptions}
component={ReactSelect}
/>
</div>
<div className="col-small pull-left time-mode">
<Field
name="period"
placeholder="AM/PM"
options={periodOptions}
component={ReactSelect}
/>
</div>
</div>
</div>
</div>
<div className="field-row">
<strong className="label">
<label>Description</label>
</strong>
<Field
name="description"
component={Input}
className="field"
bsClass="form-control input-lg"
componentClass="textarea"
/>
</div>
<button
type="submit"
className="btn btn-default btn-submit pull-right"
>
{isSubmitting
? <span><LoadingSpinner showOnlyIcon size={20} /></span>
: <span>Submit</span>
}
</button>
</div>
</div>
</form>
</div>
</div>
</Modal.Body>
</Modal>
</div>
);
}
}

Related

React component does not update automatically in UI but only in array

I'm a newbie to react and I have a problem. When I try to add a new item to my list, the component updates but "closes", in fact to see the new item added to the list, I have to click again on the router link. At that point it updates. I have this problem in both the 'todos' component and the 'subtodos' component.
How can I do?
Data structure:
const datas = [
{
categoryTitle: "oggi",
categoryId: "1",
todos: [
{
todoName: "pulire",
todoId: "a",
completed: false,
subTodos: [
{ subTodoName: "pulire bagno", subTodoId: "a1", completed: false },
{ subTodoName: "pulire cucina", subTodoId: "a2", completed: false },
],
},
{
todoName: "cucinare",
todoId: "b",
completed: false,
subTodos: [
{ subTodoName: "cucinare primo", subTodoId: "a3", completed: false },
{
subTodoName: "cucinare secondo",
subTodoId: "a4",
completed: false,
},
],
},
],
},
{
categoryTitle: "pianificato",
categoryId: "2",
todos: [
{
todoName: "studiare",
todoId: "c",
completed: false,
subTodos: [
{
subTodoName: "studiare geometria",
subTodoId: "a1",
completed: false,
},
{
subTodoName: "studiare matematica",
subTodoId: "a2",
completed: false,
},
],
},
{
todoName: "spesa",
todoId: "d",
completed: false,
subTodos: [
{ subTodoName: "zucchine", subTodoId: "a3", completed: false },
{ subTodoName: "pasta", subTodoId: "a4", completed: false },
],
},
],
},
];
export default datas;
app.js
import "./App.css";
import {
Routes,
Router,
Route,
NavLink,
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
} from "react-router-dom";
import { createContext, useState } from "react";
import CategoryLayout from "./layouts/CategoryLayout";
import Todos from "./pages/Todos";
import datas from "./datas/datas";
import SubTodos from "./pages/SubTodos";
export const DataContext = createContext();
function App() {
const [todoDatas, setTodoDatas] = useState(datas);
const [selectedItem, setSelectedItem] = useState();
const [todoId, setTodoId] = useState();
const [selectedTodo, setSelectedTodo] = useState();
return (
<DataContext.Provider
value={{
todoDatas,
setTodoDatas,
selectedItem,
setSelectedItem,
todoId,
setTodoId,
selectedTodo,
setSelectedTodo,
}}
>
<Routes>
<Route path="/" element={<CategoryLayout />}>
<Route path="todos" element={<Todos />}>
<Route path="subtodos" element={<SubTodos />} />
</Route>
</Route>
</Routes>
</DataContext.Provider>
);
}
export default App;
category layout
import React, { useContext, useState } from "react";
import { Outlet, Link } from "react-router-dom";
import { DataContext } from "../App";
import uniqid from "uniqid";
function CategoryLayout() {
const { todoDatas, setTodoDatas, setSelectedItem } = useContext(DataContext);
const [categoryInput, setcategoryInput] = useState(null);
const catchCategory = (value) => {
setcategoryInput(value);
};
const resetCategory = () => {
setcategoryInput("");
};
const addCategory = () => {
if (
categoryInput !== null &&
categoryInput !== "" &&
categoryInput !== undefined
) {
setTodoDatas((prev) => {
return [
...prev,
{
categoryTitle: categoryInput,
categoryId: uniqid(),
todos: [],
},
];
});
}
};
return (
<div className="app_wrap">
<div className="categories_wrap">
<h5>jessicamoretti#gmail.com</h5>
<input type="text" placeholder="search"></input>
{todoDatas?.map((category) => {
return (
<Link
to="todos"
key={category.categoryId}
id={category.categoryId}
onClick={() => {
setSelectedItem(
todoDatas.find((cat) => {
return cat.categoryId === category.categoryId;
})
);
}}
>
{category.categoryTitle}
</Link>
);
})}
<form
onClick={(e) => {
e.preventDefault();
addCategory();
}}
>
<input
type="text"
onChange={(e) => {
catchCategory(e.target.value);
}}
></input>
<button onClick={resetCategory}>Nuovo elenco</button>
</form>
</div>
<section>
<Outlet />
</section>
</div>
);
}
export default CategoryLayout;
todos
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import { DataContext } from "../App";
import { useContext, useState } from "react";
import { Link, Outlet } from "react-router-dom";
import uniqid from "uniqid";
function Todos() {
const { setSelectedItem, selectedItem, todoId, setTodoId } =
useContext(DataContext);
const [todoInput, setTodoInput] = useState();
const catchTodo = (value) => {
setTodoInput(value);
};
const resetTodo = () => {
setTodoInput("");
};
const addTodo = (obj) => {
if (todoInput !== null && todoInput !== "" && todoInput !== undefined) {
setSelectedItem(
selectedItem?.todos?.push({
todoName: todoInput,
todoId: uniqid(),
completed: false,
subTodos: [],
})
);
}
};
console.log("todoid!", todoId);
return (
<div className="todos_subtodos_wrap">
<div className="todos_wrap">
{selectedItem?.todos?.map((todo) => {
return (
<div className="todo" key={todo.todoId}>
<Link
to="subtodos"
id={todo.todoId}
onClick={() => {
setTodoId(todo.todoId);
}}
>
{todo.todoName}
</Link>
<p>subtodo: 0 of {todo.subTodos?.length} </p>
</div>
);
})}
<form
className="todos_form"
onClick={(e) => {
e.preventDefault();
addTodo();
}}
>
<input
type="text"
className="todos_input"
placeholder="add todos"
onChange={(e) => {
catchTodo(e.target.value);
}}
></input>
<button className="add_todos" onClick={resetTodo}>
add todos
</button>
</form>
</div>
<Outlet />
</div>
);
}
export default Todos;
subtodos
import React, { useEffect } from "react";
import { useContext, useState } from "react";
import { DataContext } from "../App";
import uniqid from "uniqid";
function SubTodos() {
const {
setSelectedItem,
selectedItem,
todoId,
setTodoId,
selectedTodo,
setSelectedTodo,
} = useContext(DataContext);
const [subTodoInput, setSubTodoInput] = useState();
const [subTodos, setSubTodos] = useState(selectedTodo);
let selected = selectedItem?.todos?.find((el) => {
return el.todoId === todoId;
});
console.log(selectedItem, "selected item");
useEffect(() => {
setSelectedTodo(selected); // contiene il todo selezionato
}, [selected, setSelectedTodo]);
const catchSubTodo = (value) => {
setSubTodoInput(value);
};
const addSub = () => {
if (
subTodoInput !== null &&
subTodoInput !== "" &&
subTodoInput !== undefined
) {
setSelectedTodo(
selected?.subTodos?.push({
subTodoName: subTodoInput,
subTodoId: uniqid(),
completed: false,
})
);
}
};
return (
<div>
<div>
{selectedTodo?.subTodos?.map((el) => {
return (
<div key={el.subTodoId}>
<p> {el.subTodoName}</p>
</div>
);
})}
<form
onClick={(e) => {
e.preventDefault();
addSub();
}}
>
<input
type="text"
placeholder="+ Aggiungi sottoattività"
onChange={(e) => {
e.preventDefault();
catchSubTodo(e.target.value);
}}
></input>
<button
onClick={(e) => {
e.preventDefault();
}}
></button>
</form>
</div>
</div>
);
}
export default SubTodos;

Getting value of SetState not updated in react

I am having a callback function which updates my array in the SetState and it works fine as I can see in the console log in the handleDaysValueChange all is good. But when I try to access it in another function i.e handleValuesChange the values are not up to date. I am missing a async or something.
import React from 'react';
import PropTypes from 'prop-types';
import AppService from 'services/app-service';
import Enum from 'enum';
import { ApiData } from 'data';
import { isEmpty, boolValue } from 'core/type-check';
import { notifySuccess } from 'core/errors';
import { withDrawerForm } from 'components/_hoc';
import { ExtendedAvatar } from 'components/extended-antd';
import { Tabs } from 'antd';
import { FormTemplateAudit } from '../templates';
import ShiftRosterFormDetails from './shiftroster-form-details';
import Item from 'antd/lib/list/Item';
const { TabPane } = Tabs;
class ShiftRosterForm extends React.Component {
constructor(props) {
super(props);
this.formInputRef = React.createRef();
this.state = {
daysValue:[]
};
this.handleDaysValueChange = this.handleDaysValueChange.bind(this);
}
handleDaysValueChange(daysValues) {
this.setState({ daysValue: daysValues }, () => {
console.log("Data" + this.props.customData);
console.log("Hello World " + JSON.stringify(this.state.daysValue));
});
}
componentDidMount() {
// Update the parent form state with a reference to the
// formInputRef so we can call this later for validation
// before saving the data to the server.
const { onFormStateChange } = this.props;
onFormStateChange({ formInputRef: this.formInputRef.current });
}
//Shift Roster Detail
handleValuesChange = (props, changedValues, allValues) => {
// Update the parent form state with the changed item
// details and mark the form as dirty
const { itemData, onFormStateChange } = this.props;
console.log("Hey" + this.state.daysValue);
onFormStateChange({
isFormDirty: true,
itemData: {
...itemData,
...changedValues,
...this.state.daysValue
}
});
}
render() {
const { itemData, customData } = this.props;
const isSR = (!isEmpty(itemData) && itemData.id > 0);
return (
<Tabs className="bhp-tabs" defaultActiveKey="1" animated={false}>
<TabPane key="1" tab="Details">
<ShiftRosterFormDetails
ref={this.formInputRef}
dataSource={itemData}
onValuesChange={this.handleValuesChange}
handleDaysValueChange={this.handleDaysValueChange}
/>
</TabPane>
<TabPane key="2" tab="Audit" disabled={!isSR}>
<FormTemplateAudit
itemData={itemData}
/>
</TabPane>
</Tabs>
);
}
}
ShiftRosterForm.propTypes = {
itemId: PropTypes.number, // Passed in by the HOC. The loaded Shift Roster id
itemData: PropTypes.object, // Passed in by the HOC. The loaded Shift Roster data
customData: PropTypes.object, // Temporary store to hold the changed Shift Roster
isFormDirty: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is dirty
isLoading: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is loading
daysValue: PropTypes.object,
onFormStateChange: PropTypes.func // Passed in by the HOC. Callback to update the parent form state.
};
ShiftRosterForm.defaultProps = {
itemId: -1,
itemData: {},
customData: {},
isFormDirty: false,
isLoading: false,
daysValue: {},
onFormStateChange() { }
};
const ShiftRosterFormTitle = ({ data }) => {
const name = (!isEmpty(data) && data.id > 0) ? `${data.name}` : 'New Shift Roster';//`${data.name}`
return isEmpty(data)
? <ExtendedAvatar type="icon" size="large" />
: <span><ExtendedAvatar name={name} type="letter" size="large" />{name}</span>
}
const saveShiftRoster = (shiftrosterId, shiftroster, rosterdays) => {
return ApiData.saveShiftRoster(shiftrosterId, shiftroster, rosterdays)
.then(response => {
notifySuccess('Save Successful', 'Site Work Package saved successfully');
return response;
})
.catch(error => {
throw error;
});
}
const saveForm = (formState, setFormState) => {
const { isFormDirty, itemData, customData, formInputRef } = formState;
const typeName = "Dynamic";
const actualType = itemData.type;
let rosterdays = [];
if (actualType !== typeName) {
rosterdays = GetDaysForRoster(itemData);
console.log("My Values" + JSON.stringify(rosterdays));
}
const shiftRosterId = itemData.id;
const isExistingShiftRoster = shiftRosterId > 0;
return new Promise((resolve, reject) => {
if (isExistingShiftRoster && !isFormDirty) {
// No Changes
notifySuccess('Save Successful', 'Site Work Package saved successfully');
resolve(itemData);
}
else {
// Validate and Save
formInputRef.validateFields((error, values) => {
if (!error) {
// Form validated successfully.
// Save form changes
const shiftrosterRecord = saveShiftRoster(shiftRosterId, values, rosterdays);
resolve(shiftrosterRecord);
}
else {
// Form validation error.
// Return data as is.
resolve(itemData);
}
});
}
});
}
const GetDaysForRoster = (itemsData) => {
const result = [];
const keys = Object.keys(itemsData);
for (const k in keys) {
if (Number(k) == k) {
result[k] = itemsData[k]
}
}
return result.filter(function (el) { return el != null });
}
const WrappedShiftRosterForm = withDrawerForm({
containerClassName: 'bhp-equipment-type-form',
title: (record) => <ShiftRosterFormTitle data={record} />,
onLoad: (itemId, setFormState) => ApiData.getShiftRoster(itemId),
onSave: (formState, setFormState) => { return saveForm(formState, setFormState); },
canView: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Read] }),
canCreate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Create] }),
canUpdate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Update] })
})(ShiftRosterForm);
WrappedShiftRosterForm.propTypes = {
containerClassName: PropTypes.string,
itemId: PropTypes.number,
visible: PropTypes.bool,
onSave: PropTypes.func,
onClose: PropTypes.func
};
WrappedShiftRosterForm.defaultProps = {
containerClassName: null,
itemId: -1,
visible: false,
onSave() { },
onClose() { }
};
export default WrappedShiftRosterForm;
//ShiftRosterFormDetails
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ApiData } from 'data';
import { Form, Input, Select, Button, space, InputNumber } from 'antd';
import ShiftDays from './shiftdays'
const ShiftRosterFormDetails = ({ form, dataSource, onValueChange, handleDaysValueChange }) => {
const { getFieldDecorator } = form;
const formLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
console.log("My datasource" + dataSource.shiftRosterDays);
//const daysRoster = dataSource.shiftRosterDays || [{ daysIn: 1, daysOut: 1, category: "Day Shift" }];
const [inputList, setInputList] = useState([{ daysIn: 1, daysOut: 1, category: "Day Shift" }]);
const [selectedType, setSelectedType] = useState(dataSource.type || 'Fixed Single');
const [isTotalDaysRequired, SetTotalDaysRequired] = useState(dataSource.type === 'Dynamic' ? true : false);
const [isTotalDaysRequiredMessage, setIsTotalDaysRequiredMessage] = useState(dataSource.type === 'Dynamic' ? 'Please enter the Total Days' : '');
const handleTypeChanged = (value, e) => {
setSelectedType(value);
if (value === "Dynamic") {
SetTotalDaysRequired(true);
setIsTotalDaysRequiredMessage('Please enter the Total Days');
}
if (value === "Fixed Single") {
if (inputList.length > 1) {
const list = [...inputList];
console.log("Total" + inputList.length);
list.splice(1, inputList.length);
setInputList(list);
console.log("My List" + JSON.stringify(list));
console.log("Input List" + JSON.stringify(inputList));
handleDaysValueChange(list);
}
}
else {
SetTotalDaysRequired(false);
setIsTotalDaysRequiredMessage('');
}
};
return (
<div className='bhp-equipment-type-form-details bhp-content-box-shadow'>
<Form {...formLayout}>
<Form.Item label='Name' hasFeedback>
{getFieldDecorator('name', {
initialValue: dataSource.name,
rules: [{
required: true,
message: 'Please enter the Name'
}],
})(
//disabled={dataSource.id > 0}
<Input placeholder='Name' />
)}
</Form.Item>
<Form.Item label='Status' hasFeedback>
{getFieldDecorator('status', {
initialValue: dataSource.status,
rules: [{
required: true,
message: 'Please enter the Status'
}],
})(
<Select>
<Select.Option value="Active">Active</Select.Option>
<Select.Option value="InActive">InActive</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Type' hasFeedback>
{getFieldDecorator('type', {
initialValue: dataSource.type || 'Fixed Single',
rules: [{
required: true,
message: 'Please select the Type'
}],
})(
<Select onChange={handleTypeChanged}>
<Select.Option value="Fixed Single">Fixed Single</Select.Option>
<Select.Option value="Fixed Multiple">Fixed Multiple</Select.Option>
<Select.Option value="Dynamic">Dynamic</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Total Days' hasFeedback style={selectedType === 'Dynamic' ? { display: '' } : { display: 'none' }}>
{getFieldDecorator('totaldays', {
initialValue: dataSource.totalDays,
rules: [{
required: isTotalDaysRequired,
message: isTotalDaysRequiredMessage
}],
})(
<InputNumber min={1} max={365} />
)}
</Form.Item>
<ShiftDays inputList={inputList} setInputList={setInputList} selectedType={selectedType} handleDaysValueChange={handleDaysValueChange} getFieldDecorator={getFieldDecorator} />
</Form>
</div>
)};
const onFieldsChange = (props, changedFields, allFields) => {
if (props.onFieldsChange) {
props.onFieldsChange(props, changedFields, allFields);
}
};
const onValuesChange = (props, changedValues, allValues) => {
if (props.onValuesChange) {
props.onValuesChange(props, changedValues, allValues);
}
};
ShiftRosterFormDetails.propTypes = {
form: PropTypes.object,
dataSource: PropTypes.object,
onFieldsChange: PropTypes.func,
onValuesChange: PropTypes.func
};
ShiftRosterFormDetails.defaultProps = {
form: {},
dataSource: {},
onFieldsChange() { },
onValuesChange() { }
};
export default Form.create({
onValuesChange,
onFieldsChange
})(ShiftRosterFormDetails);

componentWillReceiveProps do not fire

I have the following Higher Order Component to create a loading on my components:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Spinner from 'components/spinner';
const WithSpinner = (WrappedComponent) => {
return class SpinnerWrapper extends Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render (){
const { loading } = this.props;
return (
<div>
{loading ?
<Spinner />
:
<WrappedComponent {...this.props}>{this.props.children}</WrappedComponent>}
</div>)
}
}
SpinnerWrapper.propTypes = {
loading: PropTypes.bool,
};
SpinnerWrapper.defaultProps = {
loading: true,
};
return SpinnerWrapper;
};
export default WithSpinner;
and I have the following component who is wrapping in WithSpinner
const updateChartSeries = answersSummary => ({
chartSeries: TasksTransforms.fromAnswerSummaryToClickEffectivenessChart(answersSummary),
transposed: false,
viewOptions: { type: 'pie', stacked: false, column: false },
});
class ClickEffectivenessChart extends Component {
constructor(props) {
super(props);
this.state = {
chartSeries: [],
viewOptions: {
type: 'pie',
stacked: false,
column: false,
},
title: 'Click Effectiveness',
};
}
componentWillReceiveProps({ answersSummary }) {
this.setState(updateChartSeries(answersSummary));
}
handleChartType = (type = 'pie') => {
switch (type) {
case 'pie':
this.setState({ viewOptions: { type: 'pie', stacked: false, column: false } });
break;
case 'bar':
this.setState({ viewOptions: { type: 'bar', stacked: false, column: false } });
break;
case 'column':
this.setState({ viewOptions: { type: 'bar', stacked: false, column: true } });
break;
default:
break;
}
}
optionsDropdown = () => {
const options = [
{ onClick: () => this.handleChartType('pie'), text: 'Pie' },
{ onClick: () => this.handleChartType('bar'), text: 'Bar' },
{ onClick: () => this.handleChartType('column'), text: 'Column' },
];
return options;
}
render() {
const { chartSeries, viewOptions, title } = this.state;
return (
chartSeries.length > 0 &&
<div className="content" >
<h4>{title}</h4>
<div className="box">
<EffectivenessChart type={viewOptions.type} series={chartSeries} title={title} optionMenu={this.optionsDropdown()} stacked={viewOptions.stacked} column={viewOptions.column} transposed />
</div>
</div>
);
}
}
ClickEffectivenessChart.propTypes = {
answersSummary: PropTypes.object, //eslint-disable-line
};
ClickEffectivenessChart.defaultProps = {
answersSummary: {},
};
export default WithSpinner(ClickEffectivenessChart);
And sometimes the componentWillReceiveProps it no trigger and I can see why. I'm assuming that my WithSpinner controller have a problem but I can see what is the problem. The same behavior happen in all components wrapped with the WithSpinner controller
Any help please?
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Heatmap from 'components/heatmap';
import ClickEffectivenessChart from './click_effectiveness_chart';
import ClickEffectivenessTable from './click_effectiveness_table';
import AreaChart from './area_chart';
import SctHeatmap from './sct_heatmap';
class SctAnswersSummary extends Component {
componentDidMount() {
if (this.props.questionId) { this.fetchData(); }
}
componentDidUpdate(prevProps) {
if (prevProps.questionId !== this.props.questionId) { this.fetchData(); }
}
fetchData = () => {
const {
datacenterId, accountId, projectId, questionId,
} = this.props;
this.props.actions.questionFetchRequest(datacenterId, accountId, projectId, questionId);
this.props.actions.answersSummaryFetchRequest(datacenterId, accountId, projectId, questionId);
this.props.actions.answersSctClicksFetchRequest(datacenterId, accountId, projectId, questionId);
}
render() {
const { imageUrl, clicks, questionId, imageWidth, imageHeight } = this.props.answerSctClicks;
const { answersSummaryLoading, answersSctLoading, answersSummary } = this.props;
return (
<div className="content">
<div className="content" >
<SctHeatmap imageUrl={imageUrl} clicks={clicks} questionId={questionId} imageWidth={imageWidth} imageHeight={imageHeight} loading={answersSctLoading} />
<br />
<ClickEffectivenessChart answersSummary={answersSummary} loading={answersSummaryLoading} />
<br />
<AreaChart answersSummary={answersSummary} loading={answersSummaryLoading} />
<br />
<ClickEffectivenessTable answersSummary={answersSummary} loading={answersSummaryLoading} />
</div>
</div>
);
}
}
SctAnswersSummary.propTypes = {
datacenterId: PropTypes.string,
accountId: PropTypes.string,
projectId: PropTypes.string,
questionId: PropTypes.string,
questionSct: PropTypes.object, // eslint-disable-line react/forbid-prop-types
actions: PropTypes.shape({
questionSctFetchRequest: PropTypes.func,
}).isRequired,
answersSummaryLoading: PropTypes.bool,
answersSctLoading: PropTypes.bool,
};
SctAnswersSummary.defaultProps = {
datacenterId: null,
accountId: null,
projectId: null,
questionId: null,
questionSct: {},
answersSummaryLoading: true,
answersSctLoading: true,
};
export default SctAnswersSummary;
It's because your HOC is a function stateless component, meaning that it doesn't have any lifecycle events. You need to convert WithSpinner to a class component and then the lifecycle events should flow through to your wrapped component:
const WithSpinner = (WrappedComponent) => {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <div>
{this.props.loading ?
<Spinner />
:
<WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>}
</div>
}
}
}

React dispatching action in componentDidMount()

I've been trying to dispatch an action in componentDidMount, unfortunately I'm getting:
Maximum call stack size exceeded
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import moment from 'moment';
import styles from './style.css';
import Arrow from './components/Arrow';
import RadioCheck from 'components/RadioCheck';
import Select from 'components/Select';
import DatePickerWrapper from 'components/DatePickerWrapper';
import { months } from 'utils';
import { changeMonth, changeFromDate, changeToDate, changeRadioDatepicker } from 'components/PostsPage/actions';
class DatePickerDropdown extends Component {
constructor(props) {
super(props);
this.firstChild = null;
this.state = {
open: false,
loadedPreferences: false,
};
this._handleSelectedClick = this._handleSelectedClick.bind(this);
this._handleRadioChange = this._handleRadioChange.bind(this);
this._handleFromDatepickerChange = this._handleFromDatepickerChange.bind(this);
this._handleToDatepickerChange = this._handleToDatepickerChange.bind(this);
this._handleMonthChange = this._handleMonthChange.bind(this);
this._handleOutsideClick = this._handleOutsideClick.bind(this);
}
componentDidMount() {
window.addEventListener('click', this._handleOutsideClick, false);
setTimeout(() => {
this.loadPreferences();
},5)
}
componentWillUnmount() {
window.removeEventListener('click', this._handleOutsideClick, false);
}
loadPreferences() {
const monthId = preferences.get(preferences.keys.DATEPICKER_MONTH);
const dateFrom = preferences.get(preferences.keys.DATEPICKER_FROM);
const dateTo = preferences.get(preferences.keys.DATEPICKER_TO);
const filterType = preferences.get(preferences.keys.DATEPICKER_FILTER_TYPE);
if (monthId !== null) {
this.props.changeMonth(monthId);
}
if (dateFrom !== null) {
this.props.changeFromDate(moment(dateFrom));
}
if (dateTo !== null) {
this.props.changeToDate(moment(dateTo));
}
if (filterType !== null) {
this.props.changeRadio(filterType);
}
}
getRange() {
const { datepickerFilter, month, dateFrom, dateTo} = this.props;
if (datepickerFilter === 'month') {
return {
start: moment().month(month).startOf('month').format('D. MMMM YYYY'),
end: moment().month(month).endOf('month').format('D. MMMM YYYY')
}
}
if (datepickerFilter === 'from_to') {
return {
start: dateFrom.format('D. MMMM YYYY'),
end: dateTo.format('D. MMMM YYYY'),
};
}
}
toggleSelect(show = null) {
if (show !== null) {
this.setState(() => ({open: show}));
}
if (show === null) {
this.setState(() => ({open: !this.state.open}));
}
}
_handleSelectedClick() {
this.toggleSelect();
}
_handleOutsideClick(e) {
if (!ReactDOM.findDOMNode(this).contains(e.target) && this.state.open) {
this.toggleSelect(false);
}
}
_handleFromDatepickerChange(date) {
if (this.props.dateFrom.toDate() !== date.toDate()) {
this.props.changeFromDate(date);
preferences.store(preferences.keys.DATEPICKER_FROM, date.toDate());
}
}
_handleToDatepickerChange(date) {
if (this.props.dateTo.toDate() !== date.toDate()) {
this.props.changeToDate(date);
preferences.store(preferences.keys.DATEPICKER_TO, date.toDate());
}
}
_handleMonthChange(month) {
if (this.props.month !== month) {
this.props.changeMonth(month);
preferences.store(preferences.keys.DATEPICKER_MONTH, month);
}
}
_handleRadioChange(filterType) {
if (this.props.datepickerFilter !== filterType) {
this.props.changeRadio(filterType);
preferences.store(preferences.keys.DATEPICKER_FILTER_TYPE, filterType);
}
}
render() {
const dropdownClass = this.state.open ? styles.dropdownActive : styles.dropdown;
const dropdownButtonClass = this.state.open ? styles.selectedActive : styles.selected;
const arrowClass = this.state.open ? styles.arrowActive : styles.arrow;
const range = this.getRange();
return (
<div className={styles.container}>
<div className={dropdownButtonClass} onClick={this._handleSelectedClick}>
<div className={styles.date}>{range.start}<span>to</span>{range.end}</div>
<div className={arrowClass}>
<Arrow up={this.state.open} size={10} invert={this.props.invert}/>
</div>
</div>
<div className={dropdownClass}>
<div className={styles.datepickerRow}>
<div>
<RadioCheck label={'Filter by Month'} type="radio" id="month" name="datepicker_radio" value="month" checked={this.props.datepickerFilter === 'month'} onChange={this._handleRadioChange}/>
</div>
<div className={styles.datepickerRowInner}>
<span>Month</span>
<div className={styles.inputItem}>
<Select
options={months}
onChange={this._handleMonthChange}
optionsToShow={12}
small
defaultOption={this.props.month.toString()}
/>
</div>
</div>
</div>
<div className={styles.datepickerRow}>
<div>
<RadioCheck label={'Filter by Date range'} type="radio" id="from" name="datepicker_radio" value="from_to" onChange={this._handleRadioChange} checked={this.props.datepickerFilter === 'from_to'}/>
</div>
<div className={styles.datepickerRowInner}>
<span>from</span>
<div className={styles.inputItem}>
<DatePickerWrapper date={this.props.dateFrom} onChange={this._handleFromDatepickerChange}/>
</div>
</div>
</div>
<div className={styles.datepickerRow}>
<div className={styles.datepickerRowInner}>
<span>to</span>
<div className={styles.inputItem}>
<DatePickerWrapper date={this.props.dateTo} onChange={this._handleToDatepickerChange}/>
</div>
</div>
</div>
</div>
</div>
);
}
}
DatePickerDropdown.propTypes = {};
DatePickerDropdown.defaultProps = {};
const mapStateToProps = state => {
const { monthSelect, dateFrom, dateTo, datepickerFilter } = state.postsFilters;
return {
month: monthSelect,
dateFrom,
dateTo,
datepickerFilter
}
};
const mapDispatchToProps = dispatch => {
return {
changeMonth: (monthId) => dispatch(changeMonth(monthId)),
changeFromDate: (date) => dispatch(changeFromDate(date)),
changeToDate: (date) => dispatch(changeToDate(date)),
changeRadio: (val) => dispatch(changeRadioDatepicker(val)),
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(DatePickerDropdown);
What I'm trying to achieve is to load preferences from localStorage. If i wrap the call of this.loadPreferences() in a setTimeout() with a delay of 100ms it does work, though that doesn't feel right.
I guess the issue stems from the fact that I'm updating the same props that I'm mapping to that component. What would be a better approach to achieve my goal?
EDIT: added whole source to avoid confusion
Try to explicitly bind click hanlder to context this._handleOutsideClick.bind(this)

React - Multiple components with the same actions & reducers

I created a component that lets you add/remove additional dropdowns onClick of a button. I use Redux to keep the state of the added fields and value selected.
It works fine but if I add the component twice on the page (using the same actions and reducers), both dropdowns will update at the same time.
How could I make them work independently?
index.jsx
import React from 'react'
import { connect } from 'react-redux'
import DropDownField from './form/drop-down-field'
import uuidV4 from 'uuid-v4'
import { saveSelect, removeSelect, saveSelectValue } from './actions.js'
class Component extends React.Component {
constructor(props) {
super(props);
}
saveData(e) {
let data = {}
data[e.target.name] = e.target.value
this.context.store.dispatch(
addData(data)
)
}
addInput = (e) => {
e.preventDefault()
this.props.saveSelect({id:uuidV4()})
}
removeInput = (index, e) => {
e.preventDefault()
this.props.removeSelect(index)
}
saveSelectValue = (e, id) => {
let data = {}
data.id = id
data.value = e.target.value
this.props.saveSelectValue(data)
}
renderNationalitiesSelect = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={{
0: 'Please Select',
1: 'British',
2: 'French',
3: 'American',
4: 'Australian'
}} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
renderCountriesSelect = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={{
0: 'Please Select',
1: 'United Kingdom',
2: 'France',
3: 'United States',
4: 'Australia'
}} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
render(){
const selections = this.props.selections || []
let {
Nationality,
CountryOfResidence
} = this.props.store
return (
<DropDownField name="Nationality" value={Nationality} options={{
0: 'Please Select', 1: 'British', 2: 'French', 3: 'American', 4: 'Australian'
}} onChange={this.saveData.bind(this)} />
<div>
<div>
{selections.map(this.renderNationalitiesSelect)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
<DropDownField name="CountryOfResidence" value={CountryOfResidence} options={{
0: 'Please Select', 1: 'United Kingdom', 2: 'France', 3: 'United States', 4: 'Australia'
}} onChange={this.saveData.bind(this)} />
<div>
<div>
{selections.map(this.renderCountriesSelect)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
store: state.AddDropdown,
selections: state.AddDropdown.selections,
}
}
const AddDropdown = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue})(Component)
export default AddDropdown
action.js
export const ADD_DATA = 'ADD_DATA'
export const ADD_SELECT = 'ADD_SELECT'
export const REMOVE_SELECT = 'REMOVE_SELECT'
export const SAVE_SELECT_OPTION = 'SAVE_SELECT_OPTION'
export function addData(data) {
return { type: ADD_DATA, data }
}
export function saveSelect(data) {
return { type: ADD_SELECT, data }
}
export function removeSelect(data) {
return { type: REMOVE_SELECT, data }
}
export function saveSelectValue(data) {
return { type: SAVE_SELECT_OPTION, data }
}
reducer.js
import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT, SAVE_SELECT_OPTION } from './actions'
function AddDropdown(state = { selections: []}, action = {}){
switch (action.type){
case ADD_DATA:
return ObjectAssign({}, state, action.data)
case ADD_SELECT:
return {
...state,
selections: [].concat(state.selections, action.data),
}
case REMOVE_SELECT:
return {
...state,
selections: state.selections.filter((selection, index) => (index !== action.data)),
}
case SAVE_SELECT_OPTION:
return {
...state,
selections: state.selections.map((selection) => selection.id === action.data.id ? action.data : selection)
}
default:
return state
}
}
const FormApp = combineReducers({
AddDropdown
})
export default FormApp
I suggest isolating each set of dropdowns as a seperate component, then working on isolating each one's redux state. My library, redux-subspace was designed for this purpose.
index.jsx
import React from 'react'
import { connect } from 'react-redux'
import { SubspaceProvider } from 'redux-subspace'
import DropDownField from './form/drop-down-field'
import uuidV4 from 'uuid-v4'
import { saveSelect, removeSelect, saveSelectValue } from './actions.js'
class Component extends React.Component {
constructor(props) {
super(props);
}
saveData(e) {
let data = {}
data[e.target.name] = e.target.value
this.context.store.dispatch(
addData(data)
)
}
addInput = (e) => {
e.preventDefault()
this.props.saveSelect({id:uuidV4()})
}
removeInput = (index, e) => {
e.preventDefault()
this.props.removeSelect(index)
}
saveSelectValue = (e, id) => {
let data = {}
data.id = id
data.value = e.target.value
this.props.saveSelectValue(data)
}
renderSelections = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={this.props.options} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
render(){
return (
<div>
<DropDownField name={this.props.name} value={this.props.store.value} options={this.props.options} onChange={this.saveData.bind(this)} />
<div>
{this.props.selections.map(this.renderSelections)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
store: state,
selections: state.selections,
}
}
const SingleAddDropdown = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue})(Component)
const AddDropdown = () => {
return (
<div>
<SubspaceProvider mapState={state => state.nationality} namespace="nationalities">
<SingleAddDropdown name="Nationality" options={{
0: 'Please Select',
1: 'British',
2: 'French',
3: 'American',
4: 'Australian'
}}/>
</SubspaceProvider>
<SubspaceProvider mapState={state => state.countryOfResidence} namespace="countryOfResidence">
<SingleAddDropdown name="Country of Residence" options={{
0: 'Please Select',
1: 'United Kingdom',
2: 'France',
3: 'United States',
4: 'Australia'
}}/>
</SubspaceProvider>
</div>
)
}
export default AddDropdown
reducer.js
import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { namespaced } from 'redux-subspace'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT, SAVE_SELECT_OPTION } from './actions'
function AddDropdown(state = { selections: []}, action = {}){
switch (action.type){
case ADD_DATA:
return ObjectAssign({}, state, action.data)
case ADD_SELECT:
return {
...state,
selections: [].concat(state.selections, action.data),
}
case REMOVE_SELECT:
return {
...state,
selections: state.selections.filter((selection, index) => (index !== action.data)),
}
case SAVE_SELECT_OPTION:
return {
...state,
selections: state.selections.map((selection) => selection.id === action.data.id ? action.data : selection)
}
default:
return state
}
}
const FormApp = combineReducers({
namespaced(AddDropdown, "nationality"),
namespaced(AddDropdown, "countryOfResidence")
})
export default FormApp
NOTE: as per my comment there are some issues with this code and I have not attempted to clean them up for this example.

Categories

Resources