提交表单时,复选框答案不会保存



我显然是一个初学者,在提交时最难保存页面的复选框答案。下面是页面的代码(很抱歉太长了,复选框从314开始(。我的直觉是,我错过了一系列代码,但我一辈子都不知道它会是什么。提前感谢,我欠论坛上的每一个人最感激的!

编辑:我在修改后的问题中包含了FieldCheckBox代码。

import React, { Component } from 'react';
import { bool, string } from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from '../../util/reactIntl';
import { Field, Form as FinalForm, FormSpy } from 'react-final-form';
import isEqual from 'lodash/isEqual';
import classNames from 'classnames';
import { ensureCurrentUser } from '../../util/data';
import config from '../../config';
import arrayMutators from 'final-form-arrays';
import { propTypes } from '../../util/types';
import * as validators from '../../util/validators';
import { isUploadImageOverLimitError } from '../../util/errors';
import { Form, Avatar, Button, ImageFromFile, IconSpinner, FieldTextInput, FieldCheckbox } from '../../components';
import css from './ProfileSettingsForm.module.css';
const ACCEPT_IMAGES = 'image/*';
const UPLOAD_CHANGE_DELAY = 2000; // Show spinner so that browser has time to load img srcset

class ProfileSettingsFormComponent extends Component {
constructor(props) {
super(props);
this.uploadDelayTimeoutId = null;
this.state = { uploadDelay: false };
this.submittedValues = {};
}
componentDidUpdate(prevProps) {
// Upload delay is additional time window where Avatar is added to the DOM,
// but not yet visible (time to load image URL from srcset)
if (prevProps.uploadInProgress && !this.props.uploadInProgress) {
this.setState({ uploadDelay: true });
this.uploadDelayTimeoutId = window.setTimeout(() => {
this.setState({ uploadDelay: false });
}, UPLOAD_CHANGE_DELAY);
}
}
componentWillUnmount() {
window.clearTimeout(this.uploadDelayTimeoutId);
}
render() {
return (
<FinalForm
{...this.props}
render={fieldRenderProps => {
const {
className,
currentUser,
handleSubmit,
intl,
invalid,
onImageUpload,
pristine,
profileImage,
rootClassName,
updateInProgress,
updateProfileError,
uploadImageError,
uploadInProgress,
form,
values,
} = fieldRenderProps;


const user = ensureCurrentUser(currentUser);
// First name
const firstNameLabel = intl.formatMessage({
id: 'ProfileSettingsForm.firstNameLabel',
});
const firstNamePlaceholder = intl.formatMessage({
id: 'ProfileSettingsForm.firstNamePlaceholder',
});
const firstNameRequiredMessage = intl.formatMessage({
id: 'ProfileSettingsForm.firstNameRequired',
});
const firstNameRequired = validators.required(firstNameRequiredMessage);
// Last name
const lastNameLabel = intl.formatMessage({
id: 'ProfileSettingsForm.lastNameLabel',
});
const lastNamePlaceholder = intl.formatMessage({
id: 'ProfileSettingsForm.lastNamePlaceholder',
});
const lastNameRequiredMessage = intl.formatMessage({
id: 'ProfileSettingsForm.lastNameRequired',
});
const lastNameRequired = validators.required(lastNameRequiredMessage);
// Bio
const bioLabel = intl.formatMessage({
id: 'ProfileSettingsForm.bioLabel',
});
const bioPlaceholder = intl.formatMessage({
id: 'ProfileSettingsForm.bioPlaceholder',
});
//Amenities

const uploadingOverlay =
uploadInProgress || this.state.uploadDelay ? (
<div className={css.uploadingImageOverlay}>
<IconSpinner />
</div>
) : null;
const hasUploadError = !!uploadImageError && !uploadInProgress;
const errorClasses = classNames({ [css.avatarUploadError]: hasUploadError });
const transientUserProfileImage = profileImage.uploadedImage || user.profileImage;
const transientUser = { ...user, profileImage: transientUserProfileImage };
// Ensure that file exists if imageFromFile is used
const fileExists = !!profileImage.file;
const fileUploadInProgress = uploadInProgress && fileExists;
const delayAfterUpload = profileImage.imageId && this.state.uploadDelay;
const imageFromFile =
fileExists && (fileUploadInProgress || delayAfterUpload) ? (
<ImageFromFile
id={profileImage.id}
className={errorClasses}
rootClassName={css.uploadingImage}
aspectRatioClassName={css.squareAspectRatio}
file={profileImage.file}
>
{uploadingOverlay}
</ImageFromFile>
) : null;
// Avatar is rendered in hidden during the upload delay
// Upload delay smoothes image change process:
// responsive img has time to load srcset stuff before it is shown to user.
const avatarClasses = classNames(errorClasses, css.avatar, {
[css.avatarInvisible]: this.state.uploadDelay,
});
const avatarComponent =
!fileUploadInProgress && profileImage.imageId ? (
<Avatar
className={avatarClasses}
renderSizes="(max-width: 767px) 96px, 240px"
user={transientUser}
disableProfileLink
/>
) : null;
const chooseAvatarLabel =
profileImage.imageId || fileUploadInProgress ? (
<div className={css.avatarContainer}>
{imageFromFile}
{avatarComponent}
<div className={css.changeAvatar}>
<FormattedMessage id="ProfileSettingsForm.changeAvatar" />
</div>
</div>
) : (
<div className={css.avatarPlaceholder}>
<div className={css.avatarPlaceholderText}>
<FormattedMessage id="ProfileSettingsForm.addYourProfilePicture" />
</div>
<div className={css.avatarPlaceholderTextMobile}>
<FormattedMessage id="ProfileSettingsForm.addYourProfilePictureMobile" />
</div>
</div>
);
const submitError = updateProfileError ? (
<div className={css.error}>
<FormattedMessage id="ProfileSettingsForm.updateProfileFailed" />
</div>
) : null;
const classes = classNames(rootClassName || css.root, className);
const submitInProgress = updateInProgress;
const submittedOnce = Object.keys(this.submittedValues).length > 0;
const pristineSinceLastSubmit = submittedOnce && isEqual(values, this.submittedValues);
const submitDisabled =
invalid || pristine || pristineSinceLastSubmit || uploadInProgress || submitInProgress;

return (
<Form
className={classes}
onSubmit={e => {
this.submittedValues = values;
handleSubmit(e);
}}
>
<div className={css.sectionContainer}>
<h3 className={css.sectionTitle}>
<FormattedMessage id="ProfileSettingsForm.yourProfilePicture" />
</h3>
<Field
accept={ACCEPT_IMAGES}
id="profileImage"
name="profileImage"
label={chooseAvatarLabel}
type="file"
form={null}
uploadImageError={uploadImageError}
disabled={uploadInProgress}
>
{fieldProps => {
const { accept, id, input, label, disabled, uploadImageError } = fieldProps;
const { name, type } = input;
const onChange = e => {
const file = e.target.files[0];
form.change(`profileImage`, file);
form.blur(`profileImage`);
if (file != null) {
const tempId = `${file.name}_${Date.now()}`;
onImageUpload({ id: tempId, file });
}
};
let error = null;
if (isUploadImageOverLimitError(uploadImageError)) {
error = (
<div className={css.error}>
<FormattedMessage id="ProfileSettingsForm.imageUploadFailedFileTooLarge" />
</div>
);
} else if (uploadImageError) {
error = (
<div className={css.error}>
<FormattedMessage id="ProfileSettingsForm.imageUploadFailed" />
</div>
);
}
return (
<div className={css.uploadAvatarWrapper}>
<label className={css.label} htmlFor={id}>
{label}
</label>
<input
accept={accept}
id={id}
name={name}
className={css.uploadAvatarInput}
disabled={disabled}
onChange={onChange}
type={type}
/>
{error}
</div>
);
}}
</Field>
<div className={css.tip}>
<FormattedMessage id="ProfileSettingsForm.tip" />
</div>
<div className={css.fileInfo}>
<FormattedMessage id="ProfileSettingsForm.fileInfo" />
</div>
</div>
<div className={css.sectionContainer}>
<h3 className={css.sectionTitle}>
<FormattedMessage id="ProfileSettingsForm.yourName" />
</h3>
<div className={css.nameContainer}>
<FieldTextInput
className={css.firstName}
type="text"
id="firstName"
name="firstName"
label={firstNameLabel}
placeholder={firstNamePlaceholder}
validate={firstNameRequired}
/>
<FieldTextInput
className={css.lastName}
type="text"
id="lastName"
name="lastName"
label={lastNameLabel}
placeholder={lastNamePlaceholder}
validate={lastNameRequired}
/>
</div>
</div>
<div className={classNames(css.sectionContainer, css.bioSection)}>
<h3 className={css.sectionTitle}>
<FormattedMessage id="ProfileSettingsForm.bioHeading" />
</h3>
<FieldTextInput
type="textarea"
id="bio"
name="bio"
label={bioLabel}
placeholder={bioPlaceholder}
/>
<p className={css.bioInfo}>
<FormattedMessage id="ProfileSettingsForm.bioInfo" />
</p>
</div>
<div className={classNames(css.sectionContainer, css.amenitiesSection)}>
<h3 className={css.sectionTitle}>
<FormattedMessage id="ProfileSettingsForm.amenities" />
</h3>

<div class="checkbox container">
<FieldCheckbox id="checkbox-id1" name="checkbox-group" label="Gas Oven" value="option1" />
<FieldCheckbox id="checkbox-id2" name="checkbox-group" label="Electric Oven" value="option2" />
<FieldCheckbox id="checkbox-id3" name="checkbox-group" label="Induction Oven" value="option3" />
<FieldCheckbox id="checkbox-id4" name="checkbox-group" label="Convection Oven" value="option4" />
<FieldCheckbox id="checkbox-id5" name="checkbox-group" label="Broiler" value="option5" />
<FieldCheckbox id="checkbox-id6" name="checkbox-group" label="Double Oven" value="option6" />
<FieldCheckbox id="checkbox-id7" name="checkbox-group" label="Pizza Oven" value="option7" />
<FieldCheckbox id="checkbox-id8" name="checkbox-group" label="Cast Iron" value="option8" />
<FieldCheckbox id="checkbox-id9" name="checkbox-group" label="Pressure Cooker" value="option9" />
<FieldCheckbox id="checkbox-id10" name="checkbox-group" label="Stand Mixer" value="option10" />
<FieldCheckbox id="checkbox-id11" name="checkbox-group" label="Meat Grinder" value="option11" />
<FieldCheckbox id="checkbox-id12" name="checkbox-group" label="Kitchen Scale" value="option12" />
<FieldCheckbox id="checkbox-id13" name="checkbox-group" label="Blender" value="option13" />
<FieldCheckbox id="checkbox-id14" name="checkbox-group" label="Food Processor" value="option14" />
<FieldCheckbox id="checkbox-id15" name="checkbox-group" label="Deep Fryer" value="option15" />
<FieldCheckbox id="checkbox-id16" name="checkbox-group" label="Charcoal Grill" value="option16" />
<FieldCheckbox id="checkbox-id17" name="checkbox-group" label="Gas Grill" value="option17" />
<FieldCheckbox id="checkbox-id18" name="checkbox-group" label="Electric or Manual Smoker" value="option18" />
<FieldCheckbox id="checkbox-id19" name="checkbox-group" label="Griddle" value="option19" />
<FieldCheckbox id="checkbox-id20" name="checkbox-group" label="Pasta Roller" value="option20" />
<FieldCheckbox id="checkbox-id21" name="checkbox-group" label="Rice Cooker" value="option21" />
</div>
</div>
{submitError}
<Button
className={css.submitButton}
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
ready={pristineSinceLastSubmit}
>
<FormattedMessage id="ProfileSettingsForm.saveChanges" />
</Button>
</Form>
);
}}
/>
);
}
}
ProfileSettingsFormComponent.defaultProps = {
rootClassName: null,
className: null,
uploadImageError: null,
updateProfileError: null,
updateProfileReady: false,
};
ProfileSettingsFormComponent.propTypes = {
rootClassName: string,
className: string,
uploadImageError: propTypes.error,
uploadInProgress: bool.isRequired,
updateInProgress: bool.isRequired,
updateProfileError: propTypes.error,
updateProfileReady: bool,
// from injectIntl
intl: intlShape.isRequired,
};
const ProfileSettingsForm = compose(injectIntl)(ProfileSettingsFormComponent);
ProfileSettingsForm.displayName = 'ProfileSettingsForm';


export default ProfileSettingsForm;
import React from 'react';
import { node, string } from 'prop-types';
import classNames from 'classnames';
import { Field } from 'react-final-form';
import css from './FieldCheckbox.module.css';
const IconCheckbox = props => {
const { className, checkedClassName, boxClassName } = props;
return (
<svg className={className} width="14" height="14" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<g transform="translate(2 2)">
<path
className={checkedClassName || css.checked}
d="M9.9992985 1.5048549l-.0194517 6.9993137C9.977549 9.3309651 9.3066522 10 8.4798526 10H1.5001008c-.8284271 0-1.5-.6715729-1.5-1.5l-.000121-7c0-.8284271.6715728-1.5 1.5-1.5h.000121l6.9993246.0006862c.8284272.000067 1.4999458.671694 1.499879 1.5001211a1.5002208 1.5002208 0 0 1-.0000059.0040476z"
/>
<path
className={boxClassName || css.box}
strokeWidth="2"
d="M10.9992947 1.507634l-.0194518 6.9993137C10.9760133 9.8849417 9.8578519 11 8.4798526 11H1.5001008c-1.3807119 0-2.5-1.1192881-2.5-2.4999827L-1.0000202 1.5c0-1.3807119 1.119288-2.5 2.500098-2.5l6.9994284.0006862c1.3807118.0001115 2.4999096 1.11949 2.4997981 2.5002019-.0000018.003373-.0000018.003373-.0000096.0067458z"
/>
</g>
<path
d="M5.636621 10.7824771L3.3573694 8.6447948c-.4764924-.4739011-.4764924-1.2418639 0-1.7181952.4777142-.473901 1.251098-.473901 1.7288122 0l1.260291 1.1254782 2.8256927-4.5462307c.3934117-.5431636 1.1545778-.6695372 1.7055985-.278265.5473554.3912721.6731983 1.150729.2797866 1.6951077l-3.6650524 5.709111c-.2199195.306213-.5803433.5067097-.9920816.5067097-.3225487 0-.6328797-.1263736-.8637952-.3560334z"
fill="#FFF"
/>
</g>
</svg>
);
};
IconCheckbox.defaultProps = { className: null, checkedClassName: null, boxClassName: null };
IconCheckbox.propTypes = { className: string, checkedClassName: string, boxClassName: string };
const FieldCheckboxComponent = props => {
const {
rootClassName,
className,
svgClassName,
textClassName,
id,
label,
useSuccessColor,
...rest
} = props;
const classes = classNames(rootClassName || css.root, className);
// This is a workaround for a bug in Firefox & React Final Form.
// https://github.com/final-form/react-final-form/issues/134
const handleOnChange = (input, event) => {
const { onBlur, onChange } = input;
onChange(event);
onBlur(event);
};
const successColorVariantMaybe = useSuccessColor
? {
checkedClassName: css.checkedSuccess,
boxClassName: css.boxSuccess,
}
: {};
return (
<span className={classes}>
<Field type="checkbox" {...rest}>
{props => {
const input = props.input;
return (
<input
id={id}
className={css.input}
{...input}
onChange={event => handleOnChange(input, event)}
/>
);
}}
</Field>
<label htmlFor={id} className={css.label}>
<span className={css.checkboxWrapper}>
<IconCheckbox className={svgClassName} {...successColorVariantMaybe} />
</span>
<span className={classNames(css.text, textClassName || css.textRoot)}>{label}</span>
</label>
</span>
);
};
FieldCheckboxComponent.defaultProps = {
className: null,
rootClassName: null,
svgClassName: null,
textClassName: null,
label: null,
};
FieldCheckboxComponent.propTypes = {
className: string,
rootClassName: string,
svgClassName: string,
textClassName: string,
// Id is needed to connect the label with input.
id: string.isRequired,
label: node,
// Name groups several checkboxes to an array of selected values
name: string.isRequired,
// Checkbox needs a value that is passed forward when user checks the checkbox
value: string.isRequired,
};
export default FieldCheckboxComponent;

虽然这可能不是这个特定FieldCheckbox组件的确切解决方案,但这可能为如何使用多个复选框提供了一个简单的参考。

代码段

const MyCheckbox = ({id, name, value, label, clickHandler, idx, ...props}) => {
return (
<div key={idx}>
<input
type="checkbox"
id={id}
name={name}
value={value}
onClick={clickHandler}
/>
<label for={id}>{label}</label>
</div>
);
};
class Thingy extends React.Component {
constructor(props) {
super(props);
this.state = {
cbStatus: {}
}
};
render() {
const { cbLabels } = this.props;
const { cbStatus } = this.state;
const checkedItems = [];
Object.entries(cbStatus).forEach(([k ,v]) => {
if (v) checkedItems.push(cbLabels[k-1])
});
return (
<div>
checked Items: {checkedItems.join(', ')} <br/> <br/>
{
cbLabels && Array.isArray(cbLabels) &&
cbLabels.length > 0 && cbLabels.map((lbl, idx) => (
<MyCheckbox
id={`checkbox-id${idx+1}`}
name={'checkbox-group'}
value={`option${idx+1}`}
label={lbl}
clickHandler={() => this.setState(prev => ({
...prev,
cbStatus: {
...prev.cbStatus,
[idx+1]: !(prev.cbStatus[idx+1])
}
}))}
/>
))
}
</div>
);
};
}
const itemsList = ['Gas Oven', 'Electric Oven', 'Other Items', 'Pasta Roller', 'Rice Cooker'];
ReactDOM.render(
<div>
<h3> CheckBox Demo </h3>
<Thingy cbLabels={itemsList} />
</div>,
document.getElementById("rd")
);
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

解释

cbStatus是一个状态变量(对象(,用于跟踪选中了哪些复选框
  • MyCheckbox组件中,处理onClick事件
  • 复选框的namevalueid道具都与OP的问题类似
  • 当需要处理复选框的onClick时,上面的片段可能会有所帮助。对于表单提交这样的场景,用户界面不需要动态更改(复选框被勾选/清除除外(,可能不需要处理onClick

    最新更新