I am recently developing an email list using React Table.
There is a BCC field that allows users to insert an email address.
I have implemented App.tsx as the Root component and EmailForm.tsx as the child one.
After every action from Child (Insert characters in BCC, select the checkboxes), an update email list is sent from the Child to Parent component where all main activities will be handled.
I am facing a problem that every time I try to insert a new character in BCC input, "onChange" event just takes one character and not the whole input text area.
I followed this thread, but it did not help.
My repo:
https://codesandbox.io/s/thirsty-ellis-2981iv
My Parent component: App.tsx
import React, { Component } from "react";
import { Formik } from "formik";
import './App.css';
import EmailForm from "./EmailForm";
interface IEmail {
"title": number;
"checkList": ICheckList[];
"bcc": IBcc;
}
interface ICheckList {
"isEnable": boolean;
"email": string
}
interface IBcc {
"isEnable": boolean;
"email": string
}
const defaults = [
{
title: "title-1",
checkList: [{
isEnable: true,
email: "title-1.1#mail.com"
},
{
isEnable: true,
email: "title-1.2#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
},
{
title: "title-2",
checkList: [{
isEnable: true,
email: "title-2#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
},
{
title: "title-3",
checkList: [{
isEnable: true,
email: "title-3#mail.com"
}],
bcc: {
isEnable: true,
email: ""
}
}
];
class App extends Component {
state = {
data: defaults,
}
getInitialValues = () => {
const initialValues = {
...defaults
};
return initialValues;
}
handleBccInput = (index: number, event: string) => {
console.log('handleBccInput index: ' + index + ' bccInput : ' + event)
let data = [...this.state.data];
//console.log('data: ' + JSON.stringify(data));
data[index].bcc.email = event;
console.log('data[index].bcc.email: ' + data[index].bcc.email);
this.setState({ data });
}
onSubmit = () => {
console.log('onSubmit clicked')
}
handleCheckboxSelected = (emailIdx: number, addressIdx: number) => {
let data = [...this.state.data];
data[emailIdx].checkList[addressIdx].isEnable = !data[emailIdx].checkList[addressIdx].isEnable
this.setState({ data });
}
render() {
const initialValues = this.getInitialValues();
const renderForm = (props: any) => (
<EmailForm
{...props}
data={this.state.data}
handleBccInput={this.handleBccInput}
handleCheckboxSelected={this.handleCheckboxSelected}
/>
);
return (
<React.Fragment >
<Formik
// tslint:disable-next-line jsx-no-lambda
render={props => renderForm(props)}
initialValues={initialValues}
onSubmit={this.onSubmit}
validateOnBlur={true}
validateOnChange={true} />
</React.Fragment>
);
}
}
export default App;
My Child component: EmailForm.tsx:
import React, { Component } from "react";
import { Form, FormikProps } from "formik";
import { WithTranslation, withTranslation } from "react-i18next";
import ReactTable, { Column } from "react-table";
import "react-table/react-table.css";
interface IEmail {
"title": number;
"checkList": ICheckList[];
"bcc": IBcc;
}
interface ICheckList {
"isEnable": boolean;
"email": string
}
interface IBcc {
"isEnable": boolean;
"email": string
}
interface IState {
data: IEmail[],
handleBccInput(index: any, event: string): any,
handleCheckboxSelected(emailIndex: number, addressIndex: number): any,
}
class EmailForm extends Component<IEmail & IState & WithTranslation> {
renderCheckbox = (title: string) => {
return (
<div>{title}</div>
);
}
overrideValue = (index: number, override: any) => {
//console.log('index: ' + index + ' override: ' + override)
this.props.handleBccInput(index, override)
}
onCheckBoxItemSelected = (emailIndex: number, addressIndex: number) => {
this.props.handleCheckboxSelected(emailIndex, addressIndex)
}
renderHeader = (title: string) => {
return (
<div
style={{
textAlign: "center",
}}
>{title}</div>
);
}
tableHeader = (): Array<Column<IEmail>> => {
// Extract transalation variable from props
return [
{
Header: this.renderHeader('Title'),
id: "title",
accessor: "title",
width: 200,
Cell: props => {
return (
<input value={props.value} readOnly></input>
)
},
},
{
Header: this.renderHeader("Check List"),
id: "checkList",
accessor: "checkList",
sortable: false,
width: 200,
resizable: true,
Cell: props => {
const cellValues = props.value
//console.log('cell.value : ' + JSON.stringify(cellValues))
return cellValues.map((item: ICheckList, index: number) => {
return (
<div>
<input type="checkbox" checked={item.isEnable} onChange={() => this.onCheckBoxItemSelected(props.index, index)} />
<a>{item.email}</a>
</div>
)
})
}
},
{
Header: this.renderHeader("BCC"),
id: "bcc",
accessor: "bcc",
Cell: props => {
return (
<input disabled={!props.value.isEnable} value={props.value.email} onChange={e => { this.overrideValue(props.index, e.target.value) }} type="text" ></input>
)
},
width: 200,
}
];
}
/*
Component render function
*/
render() {
const {
t,
data,
} = this.props
const emptyElement = () => null;
const tableHeader = this.tableHeader();
return (
<Form className="email-container-form">
<ReactTable
columns={tableHeader}
resizable={false}
data={data}
loading={false}
showPagination={false}
NoDataComponent={emptyElement}
defaultPageSize={Number.MAX_SAFE_INTEGER}
minRows={1}
/>
</Form>
)
}
}
export default withTranslation()(EmailForm);
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);
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>
);
}
}
I have a problem rendering a component with react. I'm trying to code a wizard, however I get the following error when rendering Mstep1.
Element type is invalid: expected a string (for built-in components) or a hereclass/function (for composite components) but got: object. You herelikely forgot to export your component from the file it's defined in.
To better explain, here is some code:
-- wizard.js --
import React, { Component } from 'react';
import { Mstep1, Mstep2 } from './Steps'
import { states } from './States'
import { StateMachine } from './StateMachine.js';
class Wizard extends Component {
constructor(props) {
super(props);
this.state = {
currentState: states.MSTEP1,
formSaves: [],
}
this.saveForm =this.saveForm.bind(this);
this.nextStep = this.nextStep.bind(this);
this.backStep = this.backStep.bind(this);
this.stateMachine = new StateMachine();
}
saveForm(formSave) {
let formSaves = this.state.formSaves.concat();
formSaves.push(formSave);
this.setState({
formSaves: formSaves
});
}
nextStep(desiredState) {
let currentState = this.state.currentState;
let nextState = this.stateMachine.transitionTo(currentState, desiredState);
this.setState({
currentState: nextState
});
}
backStep(desiredState) {
let currentState = this.state.currentState;
this.setState({
currentState: this.stateMachine.transitionFrom(currentState, desiredState)
});
}
//Switch Case Methode um den CurrentStep zu bestimmen
currentStep() {
console.log(this.state.currentState);
// console.log(typeof this.state.currentState);
switch (this.state.currentState) {
case states.MSTEP1:
console.log(typeof states.MSTEP1);
return <Mstep1
saveForm={this.save}
back={this.backStep}
next={this.nextStep}
/>;
break;
case states.MSTEP2:
return(<Mstep2
/>);
default:
break;
}
}
render() {
return (
<div>
{this.currentStep()}
</div>
);
}
}
export default Wizard;
--Steps.js---
import React, { Component } from 'react';
import { states } from './States.js';
import TextArea from './Select';
import SingleInput from './SingleInput';
// import Select from './Select';
export class Mstep1 extends Component {
constructor(props) {
super(props);
this.state = {
modultitel: '',
startdate: '',
enddate: '',
modulkuerzel: '',
modulDescription: ''
};
//Bindings
this.handleModultitel = this.handleModultitel.bind(this);
this.handleStartdate = this.handleStartdate.bind(this);
this.handleEnddate = this.handleEnddate.bind(this);
this.handleModulkuerzel = this.handleModulkuerzel.bind(this);
this.handlemodulDescriptionChange = this.handlemodulDescriptionChange.bind(this);
this.validate = this.validate.bind(this);
}
handleModultitel(e) {
this.setState({ modultitel: e.target.value }, () => console.log('modultitel:', this.state.modultitel));
}
handleStartdate(e) {
this.setState({ startdate: e.target.value }, () => console.log('startdate:', this.state.startdate));
}
handleEnddate(e) {
this.setState({ enddate: e.target.value }, () => console.log('enddate:', this.state.enddate));
}
handleModulkuerzel(e) {
this.setState({ modulkuerzel: e.target.value }, () => console.log('modulkuerzel', this.state.modulkuerzel));
}
handlemodulDescriptionChange(e) {
// const textArray = e.target.value.split('').filter(x => x !== 'e');
// console.log('string split into array of letters',textArray);
// const filteredText = textArray.join('');
// this.setState({ modulDescription: filteredText }, () => console.log('modulDescription', this.state.modulDescription));
this.setState({ modulDescription: e.target.value }, () => console.log('modulDescription', this.state.modulDescription));
}
handleClearForm(e) {
e.preventDefault();
this.setState({
modultitel: '',
startdate: '',
enddate: '',
modulkuerzel: '',
modulDescription: ''
});
}
validate(e) {
e.preventDefault();
this.props.saveForm({
modultitel: '',
startdate: '',
enddate: '',
modulkuerzel: '',
modulDescription: '',
});
this.props.next(this.props.nextState);
this.handleClearForm(e);
}
render() {
return (
<form>
<h5>Modul anlegen (Schritt 1 von x)</h5>
<SingleInput
inputType={'text'}
title={'Modultitel: '}
name={'name'}
controlFunc={this.handleModultitel}
content={this.state.modultitel}
placeholder={'Modultitel'} />
<SingleInput
inputType={'text'}
title={'Gültig ab: '}
name={'Startdate'}
controlFunc={this.handleStartdate}
content={this.state.startdate}
placeholder={'Startdatum'} />
<SingleInput
inputType={'text'}
title={'Gültig bis: '}
name={'Enddate'}
controlFunc={this.handleEnddate}
content={this.state.enddate}
placeholder={'Enddatum'} />
<SingleInput
inputType={'text'}
title={'Modulkürzel'}
name={'Modulkürzel'}
controlFunc={this.handleModulkuerzel}
content={this.state.modulkuerzel}
placeholder={'Modulkützel'} />
<TextArea
title={'Kurzbeschreibung'}
rows={5}
name={'Kurzbeschreibung'}
resize={false}
content={this.state.modulDescription}
controlFunc={this.handlemodulDescriptionChange}
placeholder={'Kurzbeschreibung zu Modulen'} />
<button
onClick={this.validate}>Weiter</button>
<button
onClick={this.handleClearForm}>Clear form</button>
</form>
);
}
}// Ende Class Mstep1
export class Mstep2 extends Component {
constructor(props) {
super(props);
this.state = {
modulThema: '',
themaDescription: ''
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleThemaDescription = this.handleThemaDescription.bind(this);
this.back = this.back.bind(this);
}
handleModulthema(e) {
this.setState({ modulThema: e.target.value }, () => console.log('thema: ', this.state.modulThema));
}
handleThemaDescription(e) {
this.setState({ themaDescription: e.target.value }, () => console.log('tDescription', this.state.themaDescription))
}
back(e) {
e.preventDefault();
this.props.back(states.MSTEP1);
}
validate(e) {
e.preventDefault();
this.props.saveForm({
modulThema: '',
themaDescription: ''
});
this.props.next(this.props.nextState);
this.handleClearForm(e);
}
render() {
return (
<form>
<h5>Modul anlegen (Schritt 2 von x)</h5>
<SingleInput
inputType={'text'}
title={'Neues Thema'}
name={'modulname'}
controlFunc={this.handleModulThema}
content={this.modulThema}
placeholder={'Modulthema'} />
<TextArea
title={'Beschreibung (Inhalte des Thmas)'}
rows={5}
name={'Thema-Beschreibung'}
resize={10}
controlFunc={this.handleThemaDescription}
content={this.state.themaDescription}
placeholder={''} />
<button
onClick={this.validate}>Weiter</button>
<button
onClick={this.back}>zurück</button>
<button
onClick={this.handleClearForm}>Clear form</button>
</form>
);
}
}
I would recommend you to split Mstep1 and Mstep2 into two different files and then you can export default class Mstep1 for example and import with import Mstep1 from 'Steps/Mstep1'. It's a good practice in React that you stick to one component per file. Refer to this article as a good reference to organize your react applications.
You can do export const class Mstep1 and export const Mstep2. So that you can import it like you already have.