Search code examples
reactjsvalidationmaterial-uireact-material-ui-form-validator

How to apply Form validation for React Material-UI TextField and Select?


I'm trying to add validation to TextField and Select before going to handleNext(). Here is the code (2 components):

class Quote extends React.Component {
state = {
    activeStep: 0,
    zipCode: '',
    destination: '',
    sedanPrice: '',
    suvPrice: '',
    labelWidth: 0,
};

getStepContent = (step) => {
    switch (step) {
        case 0:
            return (
                <div>
                    <QuoteForm
                        {...this.state}
                        {...this.state.value}
                        handleChange={this.handleChange}
                    />
                </div>
            )
        case 1:
            return (
                <div>
                    <QuotePrice 
                        {...this.state}
                        {...this.state.value}
                    />
                </div>
            )
        default:
            throw new Error('Unknown step');
    }
}

handleNext = () => {
    this.setState(prevState => ({
        activeStep: prevState.activeStep + 1,
    }));
    switch(this.state.activeStep){
        case 0: 
            // Creates an API call for sedan pricing
            API.getPrice(this.state.zipCode, "sedan" + this.state.destination).then(res => {
                let price = res.data;
                let key = Object.keys(price);
                console.log("Sedan price is $" + price[key]);
                this.setState({sedanPrice: price[key]});
            })
            .catch(err => console.log(err));

            // Creates an API call for SUV pricing
            API.getPrice(this.state.zipCode, "suv" + this.state.destination).then(res => {
                let price = res.data;
                let key = Object.keys(price);
                console.log("SUV price is $" + price[key])
                this.setState({suvPrice: price[key]});
            })
            .catch(err => console.log(err));
        break
        case 1:
            console.log('forward to booking page');
            window.location.href = '/booking';
        break
        default: 
            console.log('over');
    }
};

handleBack = () => {
    this.setState(state => ({
        activeStep: state.activeStep - 1,
        sedanPrice: '',
        suvPrice: '',
        destination: '',
        zipCode: '',
    }));
};

handleReset = () => {
    this.setState({
        activeStep: 0,
    });
};

handleChange = event => {
    const { name, value } = event.target;
    this.setState({
        [name]: value,
    });
};

render() {
    const { classes } = this.props;
    const { activeStep } = this.state;
    const steps = ['Pick Up Address', 'Select Your Vehicle'];

    return (
        <React.Fragment>
            <CssBaseline />
            <main className={classes.layout}>
                <Paper className={classes.paper}>
                    <React.Fragment>
                        {activeStep === steps.length ? (
                            <React.Fragment>
                                <Typography variant="h5" gutterBottom>
                                    Thank you for your interest!
                                </Typography>
                            </React.Fragment>
                            ) : (
                            <React.Fragment>
                                {this.getStepContent(activeStep)}
                                <div className={classes.buttons}>
                                    {activeStep !== 0 && (
                                        <Button onClick={this.handleBack} className={classes.button}>
                                            Back
                                        </Button>
                                    )}
                                    <Button
                                        variant="contained"
                                        color="primary"
                                        onClick={this.handleNext}
                                        className={classes.button}
                                    >
                                        {activeStep === steps.length - 1 ? 'Book Now' : 'Next'}
                                    </Button>
                                </div>
                            </React.Fragment>
                            )}
                    </React.Fragment>
                </Paper>
            </main>
        </React.Fragment>
    );
}

}

QuoteForm.js

export class QuoteForm extends React.Component {

state = {
    zipCode: this.props.zipCode,
    destination: this.props.destination,
}

render() {

    const { classes } = this.props;

    return (
        <React.Fragment>
            <Typography variant="h5" align="center">
                Enter your zip code for a quick quote
            </Typography>
            <Grid container>
                <Grid className={classes.TextField} item xs={12} sm={6}>
                    <TextField
                        required
                        id="zip"
                        name="zipCode"
                        label="Zip / Postal code"
                        fullWidth
                        autoComplete="billing postal-code"
                        value={this.props.zipCode}
                        onChange={this.props.handleChange}
                    />
                </Grid>
                <FormControl xs={12} sm={6} className={classes.formControl}>
                    <Select
                        required
                        value={this.props.destination}
                        onChange={this.props.handleChange}
                        input={<Input name="destination" />}
                        displayEmpty
                        name="destination"
                        className={classes.selectEmpty}
                    >
                        <MenuItem value="">
                            <em>Select Your Airport *</em>
                        </MenuItem>
                        <MenuItem name="SAN" value={"SAN"}>San Diego International Airport</MenuItem>
                        <MenuItem name="LAX" value={"LAX"}>Los Angeles International Airport</MenuItem>
                    </Select>
                </FormControl>
            </Grid>
        </React.Fragment>
    );
}

}

I tried 2 different ways. First, using Button disabled and writing a function to handle the validation and set disabled to false. Second, using npm package to handle validation. Both failed since I'm new to this. Any help would be appreciated. Thanks in advance.


Solution

  • Do the following:

    • maintain a boolean state say error and errorMessage
    • in handleNext, validate he input values and set error to false and set a message to the error.
    • For material ui Textfield, use error and helperText props to set/display errors nicely beside your fields
    • For material ui Select, use FormControl error prop and provide label to Select in order to set/display errors nicely beside your fields
    • don't let the user go to next form until error is fixed.
    • pass error and errorMessage to QuoteForm component.

    Working copy of your code is here in the codesandbox

    state

    state = {
        activeStep: 0,
        zipCode: "",
        destination: "",
        sedanPrice: "",
        suvPrice: "",
        labelWidth: 0,
        error: false, //<---- here
        errorMessage: {} //<-----here
      };
    

    handleNext

    handleNext = () => {
        let isError = false;
        if (this.state.zipCode.length < 2) {
          isError = true;
          this.setState({
            error: true,
            errorMessage: { zipCode: "enter correct zipcode" }
          });
        } 
        if (this.state.destination === '') {
          isError = true;
          this.setState(prev => ({
            ...prev,
            error: true,
            errorMessage: { ...prev.errorMessage, destination: "enter correct destination" }
          }))
        }  if(!isError){
          //add else if for validating other fields (if any)
          this.setState(prevState => ({
            activeStep: prevState.activeStep + 1,
            error: false,
            errorMessage: {}
          }));
        }
      ...
    

    Mui Textfield

              <TextField
                  error={!!this.props.errorMessage.zipCode}
                  required
                  id="zip"
                  name="zipCode"
                  label="Zip / Postal code"
                  fullWidth
                  autoComplete="billing postal-code"
                  value={this.props.zipCode}
                  onChange={this.props.handleChange}
                  helperText={
                    this.props.errorMessage.zipCode &&
                    this.props.errorMessage.zipCode
                  }
                />
    

    Mui Select use

    <FormControl xs={12} sm={6} error={this.props.error}>
                <Select
                  error={!!this.props.errorMessage.destination}
                  label="enter cor dest"
                  required
                  value={this.props.destination}
                  onChange={this.props.handleChange}
                  input={<Input name="destination" />}
                  displayEmpty
                  name="destination"
                >
                  <MenuItem value="">
                    <em>Select Your Airport *</em>
                  </MenuItem>
                  <MenuItem name="SAN" value={"SAN"}>
                    San Diego International Airport
                  </MenuItem>
                  <MenuItem name="LAX" value={"LAX"}>
                    Los Angeles International Airport
                  </MenuItem>
                </Select>
                <FormHelperText>
                  {this.props.errorMessage.destination}
                </FormHelperText>
              </FormControl>