表格验证和清除

时间:2019-10-28 18:34:09

标签: 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>
  );
}

1 个答案:

答案 0 :(得分:1)

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

ReactJS中的表单会变得有些乏味,这就是为什么引入了Formikredux & redux-form之类的工具的原因。

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

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