Building a MERN Survey App, Day 4: Reactstrap Returns

Building a MERN Survey App, Day 4: Reactstrap Returns

Technically Reactstrap didn't go anywhere, but today's job was going to be to refactor without Reactstrap, while still using Bootstrap for the styling. I got most of the way through the process and discovered it doesn't really help much, so I decided to stick with Reactstrap after all. The first step, therefore, is to get Reactstrap and Formik working together on my full-size form so that all input types are covered.

This didn't require much more than the simpler form I had already worked on, but there were a couple of things that were different.

First, I had to add as="select" and as="textarea" in addition to type="select" and type="textarea". Without the as prop, Formik would not validate the fields on blur (clicking out of a field).

I also noticed that the way the fields were set up in the Formik example code, the labels weren't actually associated with their inputs because the inputs didn't have IDs. This meant, that the labels couldn't be clicked on to choose radio buttons or checkboxes, and that the labels weren't being colored red when a field didn't validate. Invalid input. Input has a red border, but the label has not changed color

Setting up the radio buttons didn't cause any issues per se, but how I wanted the validation to display turned out to be a bit tricky. The only validation radio buttons can really have is required. It wasn't difficult to set up with Yup, but it required a bit of thought about how I wanted the errors to display.

My first attempt was to add valid and invalid props to each of the individual radio buttons, and add the <FormFeedback> at the bottom. I could only get <FormFeedback> at the bottom of a <FormGroup>, so I added it to the last radio button. Radio buttons that didn't pass validation, the buttons and their labels are red

// Survey.jsx
...
 <FormGroup check>
    <Input
    tag={Field}
    id="radio1"
        name="choices"
        type="radio"
        value="one"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
    />
    <Label for="radio1" check>
        Option one is this and that—be sure to include why it's great
    </Label>
</FormGroup>
<FormGroup check>
    <Input
    tag={Field}
    id="radio2"
    name="choices"
        type="radio"
        value="two"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
    />
    <Label for="radio2" check>
        Option two can be something else and selecting it will deselect option one
    </Label>
</FormGroup>
<FormGroup check className="pb-2">
    <Input
    id="radio3"
    tag={Field}
    name="choices"
        type="radio"
        value="three"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
    />
    <Label for="radio3" check>
        Option three is not disabled
    </Label>
    <FormFeedback>{errors.choices}</FormFeedback>
</FormGroup>
...

This is fine and I could have stopped there, but I wanted to see if I could wrap the radio buttons in a fieldset and apply the validation styling to that instead. I also wanted the validation message to be a bit larger as well.

// Survey.jsx
...
<FormGroup tag="fieldset" className={errors.choices && touched.choices ? "invalid" : ""}>
    <legend>
        Make a choice
    </legend>
    <FormGroup check>
        <Input
            tag={Field}
            id="radio1"
            name="choices"
            type="radio"
            value="one"
        />
        <Label for="radio1" check>
            Option one is this and that—be sure to include why it's great
        </Label>
    </FormGroup>
    <FormGroup check>
        <Input
            tag={Field}
            id="radio2"
            name="choices"
            type="radio"
            value="two"
        />
        <Label for="radio2" check>
            Option two can be something else and selecting it will deselect option one
        </Label>
    </FormGroup>
    <FormGroup check className="pb-2">
        <Input
            id="radio3"
            tag={Field}
            name="choices"
            type="radio"
            value="three"
        />
        <Label for="radio3" check>
            Option three is not disabled
        </Label>
    </FormGroup>
    {errors.choices 
    && touched.choices 
    && <div className="error-message">{errors.choices}</div>}
</FormGroup>
...

This also required some custom CSS, so I created a file called custom.css and put it in my src folder, then imported it into index.js: import './custom.css';

/* custom.css */
fieldset,
legend {
    all: revert;
}
fieldset {
    border: 1px solid #ccc;
    border-radius: 5px;
}
legend {
    font-size: 1.8rem;
    font-weight: bold;
    padding: 0 15px;
}
fieldset.invalid {
    border-color: #dc3545;
}
fieldset.invalid legend {
    color: #dc3545;
}
.error-message {
    color: #dc3545;
    font-size: 1.2rem;
}

all: revert was new to me, it resets the css back to defaults.

Invalid radio buttons in a fieldset, the fieldset border and label are red but the radio buttons have not changed color

This doesn't use the Bootstrap validation styling at all; everything is set in my own CSS. I'm quite happy with this, but a third option would be to do both.

// Survey.jsx
...
<FormGroup tag="fieldset" className={errors.choices && touched.choices ? "invalid" : ""}>
    <legend>
        Make a choice
    </legend>
    <FormGroup check>
        <Input
            tag={Field}
            id="radio1"
            name="choices"
            type="radio"
            value="one"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
        />
        <Label for="radio1" check>
            Option one is this and that—be sure to include why it's great
        </Label>
    </FormGroup>
    <FormGroup check>
        <Input
            tag={Field}
            id="radio2"
            name="choices"
            type="radio"
            value="two"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
        />
        <Label for="radio2" check>
            Option two can be something else and selecting it will deselect option one
        </Label>
    </FormGroup>
    <FormGroup check className="pb-2">
        <Input
            id="radio3"
            tag={Field}
            name="choices"
            type="radio"
            value="three"
        valid={!errors.choices && touched.choices}
        invalid={errors.choices && touched.choices}
        />
        <Label for="radio3" check>
            Option three is not disabled
        </Label>
    </FormGroup>
    {errors.choices 
    && touched.choices 
    && <div className="error-message">{errors.choices}</div>}
</FormGroup>
...

Invalid radio buttons in a fieldset, the fieldset border and label as well as the radio buttons and their labels are all red This makes use the valid and invalid props from Reactstrap, but doesn't use <FormFeedback> (so I could make the error message larger). It's bit too much red for me, but might be preferable in some situations.

Not much has changed since yesterday; I spent a lot of time trying to figure out how to display validation of the radio buttons at the group level rather than individually, but it turned out that there's no good way built in. I'm happy enough with the solution I came up with, though, so tomorrow I'll move over to the backend so the form can actually be submitted.