javascript - 表单验证和清除

标签 javascript reactjs forms validation

我是一名刚接触 React 的前端开发人员。仍在尝试弄清楚基础知识。我正在编写一个应用程序,要求用户从弹出对话框中输入表单中的信息。当框关闭时,我希望表单完全重置/清除,如果数据有效,我希望将其添加到状态并显示在表格中。

我的表单数据输入部分已经正常工作,但我目前正在努力清除表单并实现验证。具体来说,当表单关闭(取消按钮)时,它需要清除并重置,直到对话框再次打开。当按下提交按钮时,我需要运行某种表单验证来检查每个值在添加到状态之前是否正确输入。我没有比我高的人来审查我的代码并提供提示/帮助,所以我希望有人能指出我正确的方向,并让我知道我在代码中做错了什么。

欢迎基于我的任何代码提供任何其他有用的提示。我知道这可能有点奇怪和困惑。我正在从示例中学习和构建,所以请原谅我糟糕/困惑的代码。

app.js 主要代码:

import React, { Component } from 'react';
import './App.css';
import Form from './Form'
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
import Snackbar from '@material-ui/core/Snackbar';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/styles';

const styles = theme => ({
  root: {
    container: {
      background: 'yellow'
    },
    paper: {
      background: 'red'
    },
    div:{
      background:'black'
    },
    table: {
      width: '100%'
    },
  }
});

class App extends Component {
constructor() {
  super();
  this.state = {
      dialogOpen: false,
      formClear: true,
      snackBarOpen: false,
      snackBarMessage: 'default meesage',
      tableData: {},
      formData: {
        projectName: '',
        jobNumber: '',
        accountField: {
          selected: [],
          options:[
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'},
            {value: 'test data', label: 'test data'}
          ]
        },
        projectDescription: '',
        designOrDigital: {
          options:[
            {label: 'option A', value: 'optionA'},
            {label: 'option B', value: 'optionB'}
          ],
            value: {}
        },
        mediaDeliverables: [
          {label: 'different', checked: false, quantity:'', details:''},
          {label: 'types', checked: false, quantity:'', details:''},
          {label: 'of', checked: false, quantity:'', details:''},
          {label: 'media', checked: false, quantity:'', details:''},
          {label: 'deliverables', checked: false, quantity:'', details:''},
        ],
        timing: {
          entries : [
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
            {entry: 'Dummy Values', planned:null, actual:null, notes:''},
          ]
        },
        projectMap: {
            selected: null
        }
      }
  }

}

// -----------------
//     SETTERS 
// -----------------

setAccountsSelected = (selected) => {
  let currentState = this.getStateCopy();
  currentState.formData.accountField.selected = selected;
  this.setState(currentState);
}
setProjectName = (value) => {
  let currentState = this.getStateCopy();
  currentState.formData.projectName = value;
  this.setState(currentState);
}
setJobNumber = (value) => {
  let currentState = this.getStateCopy();
  currentState.formData.jobNumber = value;
  this.setState(currentState);
}
setProjectDescription = (value) => {
  let currentState = this.getStateCopy();
  currentState.formData.projectDescription = value;
  this.setState(currentState);
}
setDesignOrDigital = (value) => {
  let currentState = this.getStateCopy();
  currentState.formData.designOrDigital.value = value;
  this.setState(currentState);
}
setMediaDeliverable = (value, index) => {
  let currentState = this.getStateCopy();
  currentState.formData.mediaDeliverables[index] = value;
  this.setState(currentState);
}
setTimingData = data => {
  let currentState = this.getStateCopy();
  currentState.formData.timing.entries = data;
  this.setState(currentState);
}
setSelectedProjectMapId = data => {
  let currentState = this.getStateCopy();
  currentState.formData.projectMap.selected = data;
  this.setState(currentState);
}
setFormClear = (value) => {
  let currentState = this.getStateCopy();
  currentState.formClear = value;
  this.setState(currentState);
}


// -----------------
//     HANDLERS 
// -----------------

onDialogOpen = () => this.setState({dialogOpen:true});

onDialogClose = () => this.setState({dialogOpen:false});

onSnackbarClose = (e, reason) => {
  if (reason === 'clickaway') {
    return;
  }

  this.setState({snackBarOpen:false}); 
  this.setState({snackBarMessage:''});
};

onCreate = () => {

  let theSnackBarMessage = `${this.state.formData.projectName} created`;
  this.setState({snackBarOpen:true});
  this.setState({snackBarMessage:theSnackBarMessage});
  this.onDialogClose();
};

// I was told to use Object.assign to avoid direct mutation of state..
getStateCopy = () => Object.assign({}, this.state); 

render() {
  const { classes } = this.props;
  return (
    <div className="App">
      <br /><br /><br />
      <Button color="primary" onClick={this.onDialogOpen}>
          Create New Resourcing Request
      </Button>
      <br /><br /><br />
      <div>Table of information will go here</div>
      <Dialog  
        className={classes.root} 
        open={this.state.dialogOpen} 
        onClose={this.onDialogClose}

        maxWidth = {'md'}
        >
      <DialogTitle>Resource Request Form</DialogTitle>
        <DialogContent>
          <Form 
            projectName={this.state.formData.projectName}
            jobNumber={this.state.formData.jobNumber} 
            accountSelected={this.state.formData.accountField.selected}
            projectDescription={this.state.formData.projectDescription}
            selectedProjectMapId={this.state.formData.projectMap.selected}

            accountFieldOptions={this.state.formData.accountField.options}
            designOrDigital={this.state.formData.designOrDigital}
            mediaDeliverablesOptions={this.state.formData.mediaDeliverables}

            timingData={this.state.formData.timing.entries}

            setAccountsSelected={this.setAccountsSelected}
            setProjectName={this.setProjectName}
            setDesignOrDigital={this.setDesignOrDigital}
            setMediaDeliverable={this.setMediaDeliverable}
            setJobNumber={this.setJobNumber}
            setProjectDescription={this.setProjectDescription}
            setTimingData={this.setTimingData}
            setSelectedProjectMapId={this.setSelectedProjectMapId}
            setFormClear={this.setFormClear}

            dialogOpen = {this.state.dialogOpen}
            formClear = {this.state.formClear}
          />  
        </DialogContent>
        <DialogActions>
          <Button onClick={this.onDialogClose} color="primary">
            Cancel
          </Button>
          <Button
            variant="contained"
            onClick={this.onCreate}
            color="primary"
          >
            Create
          </Button>
        </DialogActions>
      </Dialog>
      <Snackbar 
          open={this.state.snackBarOpen}
          message={this.state.snackBarMessage}
          onClose={this.onSnackbarClose}
          autoHideDuration={4000}
        />
    </div>
  );

  }
}

App.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(App);
//export default App;

Form.js 组件:

import React, { useState, useEffect } from 'react';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import Fab from '@material-ui/core/Fab';
import SendIcon from '@material-ui/icons/Send';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import TextField from '@material-ui/core/TextField';
import Select from '@material-ui/core/Select';
import Chip from '@material-ui/core/Chip';
import Checkbox from '@material-ui/core/Checkbox';
import Radio from '@material-ui/core/Radio';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';
import { default as MaterialRadioGroup } from '@material-ui/core/RadioGroup';
import TimingTable from './TimingTable';
import ProjectMap from './ProjectMap';

const useStyles = makeStyles(theme => ({
  container: {
    display: 'inline-block',
    flexWrap: 'wrap',
    width: 1000,
  },
  extendedIcon: {
    marginRight: theme.spacing(1),
  },
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
    maxWidth: 300,
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 370,
  },
  dense: {
    marginTop: 19,
  },
  chips: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  chip: {
    margin: 2,
  },
  noLabel: {
    marginTop: theme.spacing(3),
  },
  table: {
    width: '20%',
  }
}));

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250,
    },
  },
};

let accountNames = [];

function getStyles(name, accountName, theme) {
  return {
    fontWeight:
      accountName.indexOf(name) === -1
        ? theme.typography.fontWeightRegular
        : theme.typography.fontWeightMedium,
  };
}

const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
    <FormControl>
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
    {values.map((value, index) => (
        <MediaDeliverablesCheckBox
        key={index}
        index={index}
        mediaDeliverablesOptions={value}
        onMediaDeliverableChange={onMediaDeliverableChange(index)}
      />
      ))}
     </FormGroup>
    </FormControl>
);

const CheckboxGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

const RadioGroup = ({ value, options, name, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}

    </FormLabel>
    <MaterialRadioGroup
      name={name}
      value={value}
      onChange={onChange}
      disabled
    >
      {options.map((option, index) => (
        <FormControlLabel
          key={index}
          control={<Radio />}
          value={option.value}
          label={option.label}
        />
      ))}
    </MaterialRadioGroup>
  </FormControl>
);

export default function Form(props) {

  const classes = useStyles();
  const theme = useTheme();
  const designOrDigital = props.designOrDigital;
  const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
  const accountName = props.accountSelected;
  const projectName = props.projectName;

  accountNames = [...props.accountFieldOptions];

  useEffect(() => {
    return () => {
      //I started experimenting with lifecycle hooks because I thought
      //I might need to clear the form at a certain point in the form
      //lifcycle, but I'm not 100% sure where I need to do this..
      console.log('>> [Form.js] useEffect() = will unmount');
      console.log('>> [Form.js] will unmount: checking if form is closed and clear: dialogOpen=',props.dialogOpen,'formClear=',props.formClear);
    }
  }, []);

  useEffect(() => { 
    console.log('>> [Form.js] useEffect() = mounted or updated');
    console.log('>> [Form.js] mounted/updated: checking if form is closed and clear: dialogOpen=',props.dialogOpen,'formClear=',props.formClear);
  });

  const handleAccountChange = (event) => {
    props.setFormClear(false);
    props.setAccountsSelected(event.target.value);
  }
  const handleProjectNameChange = (event) => {
    props.setFormClear(false);
    props.setProjectName(event.target.value);
  };
  const handleJobNumberChange = (event) => {
    props.setFormClear(false);
    props.setJobNumber(event.target.value);
  };
  const handleProjectDescriptionChange = (event) => {
    props.setFormClear(false);
    props.setProjectDescription(event.target.value);
  };
  const onDesignOrDigitalChange = index => ({ target: { value } }) => {
    props.setFormClear(false);
    props.setDesignOrDigital(value);
  };
  const onMediaDeliverableChange = index => (deliverableData, e) => {
    props.setFormClear(false);
    props.setMediaDeliverable(deliverableData, index);
  };
  const timingChangeHandler = data => {
    props.setFormClear(false);
    props.setTimingData(data);
  };

  const clearForm = () => {
    console.log('>> form being cleared');
    //I couldn't figure out how to actually clear the form
    //I experimented with having a boolean value in state to
    //flag that form should be cleared.
    //I attempted to manually clear the form field values via
    //state but it doesn't appear to be working.
    props.setFormClear(true);
    props.setAccountsSelected([]); //pass empty array to clear current array
    props.setProjectName(''); //pass empty string to clear current field.
  }


  console.log('>> [Form.js] (Form) checking if form is closed and clear: ',props.dialogOpen, props.formClear)
  if(props.dialogOpen === false && props.formClear === false) { 
    //I was trying to watch the form state here to tell teh Form component
    //when it needs to clear.. it didn't work so I started experimenting with
    //lifecycle hooks above..
    console.log('>> [Form.js] (Form) Form is Closed');
    clearForm();
  }

  return (
    <div>
      <TextField
        id="project-name"
        label="Project Name"
        className={classes.textField}
        value={props.projectName}
        onChange={handleProjectNameChange}
      />
      <br /><br />
      <FormControl className={classes.formControl}>
        <InputLabel htmlFor="select-multiple-accounts">Account</InputLabel>
        <Select
          multiple
          value={accountName}
          onChange={handleAccountChange}
          input={<Input id="select-multiple-accounts" />}
          renderValue={
            selected => (
            <div className={classes.chips}>
              {
                selected.map(value => (
                <Chip key={value} label={value} className={classes.chip} />
              ))}
            </div>
          )}
          MenuProps={MenuProps}
        >
          {accountNames.map(name => (
            <MenuItem key={name.label} value={name.label} style={getStyles(name.label, accountName, theme)}>
              {name.label}
            </MenuItem>
          ))}
        </Select>
      </FormControl>
      <br /><br />
      <TextField
        id="job-number"
        label="Job #"
        className={classes.textField}
        value={props.jobNumber}
        onChange={handleJobNumberChange}
        fullWidth
      />
      <br /><br /><br />
      <TextField
        id="project-description"
        label="Project Description"
        placeholder="Provide a detailed description of the project:"
        className={classes.textField}
        multiline
        variant="outlined"
        value={props.projectDescription}
        onChange={handleProjectDescriptionChange}
        fullWidth
      />
      <br /><br /><br />
      <RadioGroup
        label="Please Choose One:"
        options={designOrDigital.options}
        value={designOrDigital.value.value}
        onChange={onDesignOrDigitalChange}
        name="designOrDigitalRadio"
      />
      <br /><br /><br />
      <MediaDeliverableCheckBoxList
        label="Please choose deliverables:"
        onMediaDeliverableChange={onMediaDeliverableChange}
        values={mediaDeliverablesOptions}
      />  
      <br /><br /><br />
      <TimingTable 
        timingData={props.timingData}
        timingChangeHandler={timingChangeHandler}
      />
      <ProjectMap 
        setSelectedProjectMapId={props.setSelectedProjectMapId}
        selectedProjectMapId={props.selectedProjectMapId}
      />
    </div>
  );
}

最佳答案

据我了解,您正在寻找一种方法使您的代码更清晰可维护,在提交时实现验证和逻辑。

ReactJS 中的表单可能会有点乏味,这就是为什么像 Formik 这样的工具和redux & redux-form已介绍。

我个人使用 Formik 在 React 中创建表单,我认为它应该被视为一个重要的 ReactJS 包。 我强烈建议您使用它,除非您有合理的理由不使用它。

Formik 可以处理所有与表单相关的逻辑,而无需编写一行代码,而且 Formik 被配置为使用 Yup这是一个用于表单对象模式验证的 npm 包。使用这些工具会让让您的生活更轻松

关于javascript - 表单验证和清除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58596566/

相关文章:

javascript - Jquery一键更新表单上的多个输入字段

javascript - 如何启用禁用 MVVM 的 Kendo DropDownList?

javascript - 通过按钮切换类(选择/取消选择)

javascript - 在 useEffect 中调用时 Hook 未更新

javascript - ReactJS 没有选择 css 样式

PHP 表单引用安全

javascript - HighCharts 动态改变图表类型

javascript - 我如何使这个 JS Turbolinks 友好?

javascript - 如何在样式组件中获取元素的宽度(以 px 为单位)?

javascript - 以响应式表单提交读取属性值