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.
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.
// 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.
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>
...
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.