How do I style the borders of a formik error field? - css

I know how to style it with regular form inputs/selects/etc, but I have switched from using those to Formik Field, and the above doesn't work the same way.
<Formik
initialValues={{
example: ''
}}
validate={(values) => {
const errors = {};
if (!values.example) errors.example = 'Required';
return errors;
}}
onSubmit={this.handleSubmit}
render={formProps => {
return (
<Form>
<Field type='text' name='example' />
<ErrorMessage name='example' />
</Form>
)
}} />
So how would I change the border of the input from whatever it is normally to red if it is empty on submit?

Solution
You can style Field and ErrorMessage components provided by Formik just like you would style any other component in react. I created a working demo for you here: https://stackblitz.com/edit/react-formik-field-error-styles
Have a look. Continue reading for explanation.
Explanation
The simplest way would be to use style prop:
function getStyles(errors, fieldName) {
if (getIn(errors, fieldName)) {
return {
border: '1px solid red'
}
}
}
...
<Field style={getStyles(formProps.errors, 'example')} type='text' name='example' />
...
However, if you need manageable customizations, I would recommend you create a custom component. Field provides you with a component prop to which you can assign your own custom component like CustomInput or something like so:
function getStyles(errors, fieldName) {
if (getIn(errors, fieldName)) {
return {
border: '1px solid red'
}
}
}
function CustomInput({ field, form: { errors } }) {
return <div>
<input {...field} style={getStyles(errors, field.name)} />
<ErrorMessage name={field.name} />
</div>
}
...
<Field component={CustomInput} type="text" name="example" />
...

When i try the technique suggested in the accepted answer for ErrorMessage component using
<ErrorMessage name="propertyName" style={{ color: 'red'}}/>
it didn't work for me. It worked when i enclosed it inside another container though.
<div style={{ color: 'red'}}>
<ErrorMessage name="propertyName" />
</div>
Hope this helps someone.

FYI, the workaround that works for styling error fields (e.g. borders) is given here:
https://stackoverflow.com/a/66395574/1005607
Pretty astonishing that Formik doesn't provide this functionality out of the box, and you have to extend <Field> with custom code.
But in general, you're not supposed to use Formik's own <Field> component. Instead you should wire Formik to a component library like Material UI or React-Bootstrap which exposes isInvalid={..} or error={..} props on its components. That will allow you to style your controls properly. Here's an example of how to wire Formik to React-Bootstrap: https://react-bootstrap.github.io/components/forms/#forms-validation-libraries If you type into a control, you'll see how its style changes depending on errors.

I've built a Component using react-bootstrap based on #gene b. answer
It gets the meta field from the useField hook and passes eventual errors to the isInvalid prop:
isInvalid={meta.touched && meta.error}
Full component for text input:
import React from 'react';
import { useField } from 'formik';
import Form from 'react-bootstrap/Form';
export default function TextInput (props) {
const [field, meta] = useField(props);
return (
<Form.Group controlId={props.name}>
<Form.Label>{props.label}</Form.Label>
<Form.Control
name={props.name}
isInvalid={meta.touched && meta.error}
{...field}
/>
<Form.Control.Feedback type="invalid">
{meta.error}
</Form.Control.Feedback>
</Form.Group>
);
};
Usage
import { Form } from 'formik';
import TextInput from './textInput';
<Form>
<TextInput name="username" label="Username"/>
</Form>
It's worth mention that exists also the isValid prop that works exactly the opposite way, adding green flag on valid fields.

Related

How to hide my Material UI button using CSS?

I am using a Material UI button for my TODO react app. I just want my form to have a input field with submit button, but I want my submit Button to invisible so that user have a feeling that my form submits on clicking "Return" key.
import { TextField, Button } from '#material-ui/core';
<form>
<TextField
id="standard-basic"
label="Write a Todo"
variant="standard"
className="textField"
value={todoInput}
onChange={(e) => {
setTodoInput(e.target.value);
}} />
<Button
type="submit"
variant="contained"
onClick={addToDo}
className="buttonDisplay">
Display
</Button>
</form>
I added display: none; to my css still it does not disappears.
.buttonDisplay{
display: none;
}
Can someone tell me what is the issue with it.
If are looking for a way to submit form without a button, take a look at this.
export default function App() {
const [todoInput, setTodoInput] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
alert(todoInput);
};
return (
<form
onSubmit={(e) => {
handleSubmit(e);
}}
>
<TextField
id="standard-basic"
label="Write a Todo"
variant="standard"
className="textField"
value={todoInput}
onChange={(e) => {setTodoInput(e.target.value)}}
/>
</form>
);
}
onSubmit will be triggered when you press 'return' key
sandbox : https://codesandbox.io/s/polished-bush-uzi6e?file=/src/App.js
You can use different method to hide a button using CSS :
modify the opacity parameter
adjust the alpha color parameter
or just change the visibility aspect of your button
Here is a link that might help you using these elements :
Ways to hide elements in CSS

React ChakraUI ForwardRef child ignoring variant prop

I'm having an issue with forwardRef and Input from ChakraUI.
I tried to do a generic input component which has always flushed variant. My problem is Chakra does reset variant to default.
ModalInput Component
import { forwardRef, InputProps, Input } from '#chakra-ui/react';
const ModalInput = forwardRef<InputProps, 'input'>((props, ref) => (
<Input ref={ref} color="gray.600" variant="flushed" {...props}>
{props.children}
</Input>
));
export default ModalInput;
ModalInput Component
<ModalInput placeholder="ex: Entreprise Dupont" />
This way, all my ModalInput should have variant flushed, but you can see on the screenshot below it is outline.
Thanks for help !
I finally discovered this bug was due to InputGroup, which was enclosing my ModalInput.
Reported this bug here.
Find the sandbox here

using material-ui for react in a wordpress plugin: css specificity problems

Im trying to properly use material-ui for react in a wordpress plugin.
Problems arise when using inputs for forms and the styling being overwritten by wordpress own styling in the forms.css file.
Im struggling to find a solution for material-ui writing css that has better specificity than forms.css without having to rewrite the styling(I dont wont my own custom styling for material-ui components: I want to use the default one)
Here is an example(text has been edited for readability(removed selectors that dont matter and styling):
react jsx:
<div id='ad-campaign-edit' style={modalStyle} className={classes.root}>
<form
className={classes.form}
noValidate
autoComplete='off'
>
<TextField
id='ad-campaign-name'
className={classes.textField}
variant='filled'
label='Navn på kampanjen'
/>
<TextField
id='ad-campaign-sub-title-text'
className={classes.textField}
variant='filled'
label='Undertekst'
/>
<TextField
id='ad-campaign-sub-price'
className={classes.textField}
variant='filled'
label='Pris'
/>
<Checkbox
defaultChecked
className={classes.textField}
color='primary'
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
</form>
</div>
forms.css(WP):
input[type="text"], {
...
}
generated css from material-ui:
.MuiInputBase-input {
...
}
other things to note: the css from material-ui is generated after the styling from wordpress in the dom.
Looking at the rules for css specificity(ref www.specifishity.com) forms.css is (0-1-1) and material-ui is (0-1-0).
My question is: how can I make material-ui take presedence without having to rewrite all the css with something like the makeStyles hook?(Can I add better specificity to material-ui in general?)
edit: Forgot to add html/jsx
edit2: rewrote specificity understanding.
I solved it, it's working but not completely.
This solution can be a starting point.
You need to install this npm
https://www.npmjs.com/package/jss-increase-specificity
Then you have to create a <StyleProvider> like described here
https://material-ui.com/styles/advanced/#jss-plugins
What I did
import { create } from 'jss';
import { StylesProvider, jssPreset } from '#material-ui/core/styles';
import increaseSpecificity from 'jss-increase-specificity';
const jss = create({
plugins: [...jssPreset().plugins, increaseSpecificity({ repeat: 1 })],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
</StylesProvider>
);
}

How can get custom tags inside web component

I'm new in webcomponents with stenciljs, I'm testing creating a select, the idea with this code create and render the select:
<rhx-select label-text="A select web component">
<rhx-select-item value="1" text="option 1"/>
<rhx-select-item value="2" text="option 2"/>
</rhx-select>
The problem i have is how can i get the tags that inside my web component?
this is my code:
import { Component, h, Prop, } from '#stencil/core';
#Component({
tag: 'rhx-select',
styleUrl: 'select.css',
shadow: true,
})
export class RhxSelect {
#Prop() labelText: string = 'select-rhx';
#Prop() id: string;
#Element() el: HTMLElement;
renderOptions() {
let data = Array.from(this.el.querySelectorAll('rhx-select-item'));
return data.map((e) =>{
<option value={e.attributes.getNamedItem('value').value}>{e.attributes.getNamedItem('text').value}</option>
});
}
render(){
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.renderOptions()}
</select>
</div>
)
}
}
Thank you for your time.
If you add the #Element() decorator you can parse the children with vanilla JS:
getItems() {
return Array.from(this.el.querySelectorAll('rhx-select-item'));
}
You can then use those elements and their properties/attributes however you want, for example to generate a list of <option> elements.
A good example is ion-select which gets the children in the childOpts() getter function.
A couple things to keep in mind:
You'll probably want to hide the items with display: none
If the options might change after the initial load you'll need to listen for those changes. Ionic uses the watchForOptions function.
this.el.querySelectorAll won't return any elements until after the component has rendered once, so that its children are available in the DOM. Therefore you will have to use something like the componentDidLoad hook:
export class RhxSelect {
// ...
#State()
items: HTMLRhxSelectItemElement[] = [];
componentDidLoad() {
this.items = Array.from(this.el.querySelectorAll('rhx-select-item'));
}
render() {
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.items.map(item => (
<option value={item.getAttribute('value')}>{item.getAttribute('text')}</option>
))}
</select>
</div>
)
}
}
Note however that componentDidLoad is only executed once, after the component has loaded. If you want your component to support dynamic changes to the options, then you'll have to use something else, like componentDidRender, but then you'll also have to make sure you don't end up with an infinite render loop. There's also a couple ways to solve this, by combining different lifecycle methods.
See https://stenciljs.com/docs/component-lifecycle for a list of all available lifecycle methods.

How to add 'required field' asterisk to angular reactive form inputs

I am developing one application with reactive dynamic angular form. This forms fields are coming from an API request and is generated dynamically.
I really need to add 'required field' asterisk (*) to my form inputs which are required. How to achieve this ?
The below is how my form fields look like.
<ng-container *ngIf="input1.type=='string'" [hidden]="False">
<div>
<mat-form-field>
<input matInput [formControlName]="input1.field_name" type="text" placeholder="{{input1.title}}">
</mat-form-field>
</div>
</ng-container>
My solution by generate new Directive:
ng generate directive directive/mark-asterisk
This is full code of directive:
import {Directive, ElementRef, Input, OnInit} from '#angular/core';
import {FormGroup} from '#angular/forms';
#Directive({
selector: '[appMarkAsterisk]'
})
export class MarkAsteriskDirective implements OnInit {
#Input() formGroup: FormGroup;
#Input() controlName: string;
constructor(private elementRef: ElementRef) {
}
ngOnInit(): void {
const isRequired = this.formGroup.controls[this.controlName]?.errors?.required;
if (isRequired) {
this.elementRef.nativeElement.innerHTML = '*';
}else{
this.elementRef.nativeElement.innerHTML = '';
}
}
}
Now in your HTML form, use it like this:
<label>Company name: <span appMarkAsterisk [formGroup]="formGroup" [controlName]="'companyName'"></span></label>
Html
<span *ngIf = "input1?.required">*</span>
.ts
You want to require a input depending on some conditions. Have a look here
Should be fixed when Angular v13 is released. https://github.com/angular/components/pull/23362
It seems like the actual validators will run from the typescript file but in order to actually get the asterisk you can just edit the html file.
<input matInput [formControlName]="input1.field_name" type="text" [placeholder]="{{input1.title}}" required>
Bind the required attribute to the input to display * on required validation from the reactive form field.
<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">
I was setting the required bit in the FormGroup, so I shouldn't need to set it in the html too. The css class ng-star-inserted was added to the class list, but the star wasn't added. I just added what I think should have been the css:
mat-label.ng-star-inserted::after {
content: " *";
}
EDIT: I looked at what angular does with template forms with the [required]="condition", and it looks like it adds a <span class="ng-star-inserted"> *</span> after the label.
I don't know why adding the css worked for me. Now that I did this test, all the other elements are marked with .ng-star-inserted. all of them inputs/labels/divs/dialogs/icons. So when I put this back in, I get stars everywhere.
Making a directive or using required attribute on the element can be a good temporary solution, but this should be working and will be fixed soon !
Material issue here: https://github.com/angular/components/issues/2574?_pjax=%23js-repo-pjax-container.
A lot of temporary solutions are discussed in the comments. I'll try to update this answer when the bug fix is released.
While we don't have a definitive solution, here is my suggestion:
#Component({
selector: "app-root",
template: `
<form [formGroup]="formGroup">
<mat-form-field>
<input
matInput
type="text"
[required]="isRequired(input1.field_name)"
[placeholder]="input1.title"
[formControlName]="input1.field_name"
/>
</mat-form-field>
</form>
`,
})
export class AppComponent {
readonly formGroup = new FormGroup({
name: new FormControl("", Validators.required),
});
readonly input1 = { field_name: "name", title: "Name", type: "string" };
isRequired(name: string): boolean {
return this.formGroup.get(name)?.hasValidator(Validators.required) ?? false;
}
}
First create a directive to add the asterisk without messing with input attributes:
import { AfterContentChecked, Directive, Optional } from "#angular/core";
import { AbstractControl } from "#angular/forms";
import { MatFormField, MatInput, MatSelect } from "#angular/material";
/**
* Input/Select into FormField consider Validator.required from reactive form if the [required] attribute is missing in the template
*/
#Directive({
selector: 'mat-form-field:has(input:not([required])), mat-form-field:has(mat-select:not([required]))'
})
export class ReactiveAsteriskDirective implements AfterContentChecked {
constructor(#Optional() private matFormField: MatFormField) { }
ngAfterContentChecked() {
if (this.matFormField) {
const ctrl = this.matFormField._control;
if (ctrl instanceof MatInput || ctrl instanceof MatSelect)
if (ctrl.ngControl)
if (ctrl.ngControl.control)
if (ctrl.ngControl.control.validator)
if (ctrl.ngControl.control.validator({} as AbstractControl))
ctrl.required = ctrl.ngControl.control.validator({} as AbstractControl).required;
}
}
}
Now you might want to make the asterisk appear with red color, since it indicates a required input. To do so, add this to your component CSS:
:host ::ng-deep .mat-placeholder-required {
color: red;
}

Resources