ReactJS Formik display error message in Red color - reactjs

In ReactJS i am developing one Class component and using Formik for validation. I am able to do validation but error message is display as a normal color (black). How to add this error message inside any HTML element (span, div,etc..).
Below is the code to validation
import React, { Component } from 'react'
import { useFormik } from 'formik';
import { Formik, FormikProps, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
export class SubmitCase extends Component {
handleSubmit = (values, {
props = this.props,
setSubmitting
}) => {
setSubmitting(false);
return;
}
render() {
const formFields = {...this.state};
return (
<Formik
initialValues={{
subject: formFields.subject
}}
validate={(values) => {
let errors = {};
if(!values.subject)
errors.subject = "subject Required";
//check if my values have errors
return errors;
}
}
onSubmit={this.handleSubmit}
render={formProps => {
return(
<Form>
<Field type="text" placeholder="First Name" name="subject" value={formFields.subject} onChange={this.changeEventReact}/>
<ErrorMessage name="subject" />
<button type="submit">
Submit Form
</button>
</Form>
);
}}
/>);
}
}
I am using to display the message. Error message is displaying as a normal text(black color text). how to change it to red color.

Form formik docs <ErrorMessage> component accepts a children prop as a function children?: ((message: string) => React.ReactNode)
so you can achieve what you want by return a component that wraps error message like this
<ErrorMessage name="subject">
{ msg => <div style={{ color: 'red' }}>{msg}</div> }
</ErrorMessage>

Also you can make css file for example - ErrorMessage.css
with body:
.validation-error-message {
color: red;
}
And make js file for example - validators.js
with body:
import './ErrorMessage.css';
export const ErrorMessageWrapper = (msg) => {
return (
<div className='validation-error-message'>
{msg}
</div>
)
}
And import, and use:
<ErrorMessage name="newPostText">
{ErrorMessageWrapper}
</ErrorMessage>

You can use component props with your custom classes in ErrorMessage.
Formik docs
e.g.
<ErrorMessage component="div" name="name" className="help-block errors" />
<ErrorMessage component="div" name="email" />
// --> {touched.email && error.email ? <div>{error.email}</div> : null}
<ErrorMessage component="span" name="email" />
// --> {touched.email && error.email ? <span>{error.email}</span> : null}
<ErrorMessage component={Custom} name="email" />
// --> {touched.email && error.email ? <Custom>{error.email}</Custom> : null}
<ErrorMessage name="email" />
// This will return a string. React 16+.
// --> {touched.email && error.email ? error.email : null}

Related

How to submit Formik when button submit outside tag formik in ReactJs?

I have a component composition as above, the box with brown color is the parent and has a blue child box as the formix container and the right side with the green color is the container where the button is placed. is it possible to submit a form with a button outside the formix tag?
I read the documentation but still not found the solution.
you can handle it using the Formik tag innerRef reference. I added a working demo pls find here.
import React, { useRef } from "react";
import { render } from "react-dom";
import { Formik } from "formik";
import * as Yup from "yup";
import "./helper.css";
const App = () => {
const formRef = useRef();
const handleSubmit = () => {
if (formRef.current) {
formRef.current.handleSubmit();
}
};
return (
<div className="app">
<Formik
innerRef={formRef}
initialValues={{ email: "" }}
onSubmit={async (values) => {
await new Promise((resolve) => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
validationSchema={Yup.object().shape({
email: Yup.string().email().required("Required")
})}
>
{(props) => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
</form>
);
}}
</Formik>
<button type="submit" onClick={handleSubmit}>
Submit
</button>
</div>
);
};
render(<App />, document.getElementById("root"));

how to do validation for a antd Text (typography) component?

It seems that we couldn't add validation for antd editable <Text> (typography).
The component doesn't accept value props like <Input> component. How do we do validation in that case?
Code:
const [title, setTitle] = useState("Battle");
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
>
<Text editable>{title}</Text>
</Form.Item>
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
Above code validation works but if the data is still there, it show validation error.
Codesandbox link : https://codesandbox.io/s/basic-antd-4-16-9-forked-o8nxdl?file=/index.js
Here is a code that does some simple validations
function reducer (action , state) {
switch (action.type){
case 'title':
return {...state , data : { ...state.data , title : action.payload }
, errors : { ...state.errors
, title : title.length > 3 null : '😈'} } // title length must be > 3
}
}
const [state , dispatch] = useReducer (reducer , {data : {} , errors : {})
console.log (state)
return ( <> <Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
onChange = {(e) => dispatch ({type : 'title' , payload : e.target.value})
>
<Text editable>{title}</Text>
</Form.Item> </>)
Before submitting make sure all errors must be null
you can handle it using the onChange like this working demo is here
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Button, Input, Typography } from "antd";
import SecForm from "./SecForm";
const { Text } = Typography;
const App = () => {
const [title, setTitle] = useState('Battle');
const data = ["1", "2", "3"];
const onHandleChange = (event) => {
setTitle(event);
};
const onSubmit = (e) => {
e.preventDefault();
console.log("Submitted");
};
return (
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
label="name"
name="name"
rules={[
{
required: true,
message: "Please input your name!"
}
]}
>
<Input placeholder="Name" />
</Form.Item>
<Form.Item
label="rank"
name="rank"
rules={[
{
required: true,
message: "Please input your rank!"
}
]}
>
<Input placeholder="Rank" />
</Form.Item>
<Form.Item
name="title"
valuePropName={title}
rules={[
{
required: title?.length < 1,
message: "Please input your title!"
}
]}
>
<Text editable={{ onChange: onHandleChange }}>{title}</Text>
</Form.Item>
{data.map((d, index) => (
<SecForm index={index} />
))}
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));

Fetch and use response to change state in React

I would like to change the state of a component based on the response of a PUT request using react-refetch.
Especially when the response of the PUT is unsuccessful, as is the case with for example a 500 response.
The following example is an example in a form. When a user submits the form it should then fire off a PUT.
If the PUT response is fulfilled, it should reset the form. Otherwise nothing should happen, and the user should be able to retry.
./MyForm.jsx
import React from "react";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
import { Formik, Form, Field, ErrorMessage } from "formik";
import ResetOnSuccess from "./ResetOnSuccess";
const MyForm = ({ settingsPut, settingsPutResponse }) => {
const submitForm = (values, formik) => {
settingsPut(true);
// Here it should pick up the settingsPutResponse,
// and then do the following ONLY if it's successful:
//
// formik.resetForm({ values });
// window.scrollTo(0, 0);
};
return (
<div>
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={dirty !== null ? !dirty : false}>
Submit
</button>
{settingsPutResponse && settingsPutResponse.rejected && (
<p style={{ color: "red" }}>Please try again</p>
)}
</Form>
)}
</Formik>
</div>
);
};
MyForm.propTypes = {
settingsPut: PropTypes.func.isRequired,
settingsPutResponse: PropTypes.instanceOf(PromiseState)
};
MyForm.defaultProps = {
userSettingsPutResponse: null
};
export default MyForm;
I might have a solution by creating a component:
./ResetOnSuccess.jsx
import React, { useEffect, useState } from "react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
const ResetOnSuccess = ({ settingsPutResponse }) => {
const { values, resetForm } = useFormikContext();
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsPutResponse && settingsPutResponse.fulfilled) {
setSuccess(true);
}
}, [settingsPutResponse]);
// only if settingsPutResponse is fulfilled will it reset the form
if (success) {
resetForm({ values });
window.scrollTo(0, 0);
setSuccess(false);
}
return null;
};
ResetOnSuccess.propTypes = { settingsPutResponse: PropTypes.instanceOf(PromiseState) };
ResetOnSuccess.defaultProps = { settingsPutResponse: null };
export default ResetOnSuccess;
And then in ./MyForm.jsx add the reset component:
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
// etc...
But since it's a component that returns a 'null'. This feels a bit like an anti-pattern.
Is there a better way?
I've created an codesandbox example here: https://codesandbox.io/s/quizzical-johnson-dberw

Use react-input-mask with antd in react-final-form

I would like to use react-input-mask with Ant Design Input in react-final-form. In order to use antd with react-final-form I also had to install redux-form-antd. So the file looks like this:
import React from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import { Form, Field } from "react-final-form";
import InputMask from "react-input-mask";
import { TextField } from "redux-form-antd";
import "antd/dist/antd.css";
const onSubmit = async values => {
window.alert(JSON.stringify(values, 0, 2));
};
const Input = props => <InputMask {...props} />;
function App() {
return (
<Form
onSubmit={onSubmit}
render={({ handleSubmit, values }) => (
<form onSubmit={handleSubmit}>
<Field
name="mask"
parse={value =>
value
.replace(/\)/g, "")
.replace(/\(/g, "")
.replace(/-/g, "")
.replace(/ /g, "")
}
render={({ input, meta }) => (
<div>
<label>mask phone</label>
<Input mask="+7 (999) 999-99-99" {...input} />
{meta.touched && meta.error && <span>{meta.error}</span>}
</div>
)}
/>
<Field
name="antd"
component={TextField}
label="antd phone"
placeholder="Phone"
/>
<Button className="submit-button" type="primary">
Send
</Button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is a codesandbox example.
I could only get to work a regular input with an InputMask (input 1) or an antd input without a mask (input 2).
How can I add an InputMask to antd input?
I've managed to use react-input-mask with antd and react-final-form without any external libraries.
Here is my component:
import React from "react";
import InputMask from "react-input-mask";
import { Input } from "antd";
import FormItem from "antd/lib/form/FormItem";
const MaskInput = props => {
const { disabled, mask, label, meta, required } = props;
return (
<FormItem
label={label}
validateStatus={
meta.touched ? (meta.error ? "error" : "success") : undefined
}
help={meta.touched ? (meta.error ? meta.error : undefined) : undefined}
hasFeedback={meta.touched ? true : false}
required={required}
>
<InputMask
mask={mask}
disabled={disabled}
autoComplete="off"
{...props.input}
>
<Input />
</InputMask>
</FormItem>
);
};
export default MaskInput;
Then it is passed to the component prop of the Field:
<Field
name="phone"
label="Phone"
component={MaskInput}
mask="+7 (999) 999-99-99"
required
/>
Here is the link to the codesandbox example.
I've never used either of those libraries, but you might want to check out using format-string-by-pattern with react-final-form's built-in parsing and formatting functionality to achieve a similar thing.
I bet you could throw redux-form-antd components into here pretty easily...

Can't type in text field using redux-form

I have a form in a modal using redux-form. I have several text fields, but you can not type in them. My suspicion is that the text field doesn't get the onChange event from the redux-form but I couldn't find any clue what am I doing good.
My code is:
import React from 'react'
import { Button, Modal, Form, Message } from 'semantic-ui-react'
import { Field, reduxForm } from 'redux-form'
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => {
console.log(input)
return (
<Form.Field>
<label>{label}</label>
<input {...input} placeholder={label} type={type} />
{touched && (error && <Message error>{error}</Message>)}
</Form.Field>
)}
let AddNewModal = (props) => {
const { handleSubmit, pristine, submitting, closeNewSite, isAddNewOpen, submit } = props
return (
<Modal dimmer='blurring' open={isAddNewOpen} onClose={closeNewSite}>
<Modal.Header>Add a new site</Modal.Header>
<Modal.Content>
<Form onSubmit={handleSubmit}>
<Form.Group widths='equal'>
<Field name='domain' type='text' component={renderField} label='Domain' />
<Field name='sitemap' type='text' component={renderField} label='Sitemap URL' />
</Form.Group>
/**
* Other fields
* /
<Button type='submit' disabled={pristine || submitting}>Save</Button>
</Form>
</Modal.Content>
<Modal.Actions>
<Button color='black' onClick={closeNewSite} content='Close' />
<Button positive icon='save' labelPosition='right' onClick={submit} content='Save' disabled={pristine || submitting} />
</Modal.Actions>
</Modal>
)
}
export default reduxForm({
form: 'newsite'
})(AddNewModal)
I added the reducer and still got the same issue. At last, I found it must add the attr 'form'.
const reducers = {
routing,
form: formReducer
};
I found the problem. I forgot to inject the redux-form's reducer.
I actually had a similar issue. I will post the code that I am working on for form validation with V6 of redux-form. It works right now but the things you want to look at are componentDidMount, handleInitialize, and handleFormSubmit. Where I figured this out link.
/**
* Created by marcusjwhelan on 10/22/16.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field } from 'redux-form'; // V6 !!!!!!!!
import { createPost } from '../actions/index';
const renderInput = ({ input, label, type, meta: {touched, invalid, error }}) => (
<div className={`form-group ${touched && invalid ? 'has-danger' : ''}`}>
<label>{label}</label>
<input className="form-control" {...input} type={type}/>
<div className="text-help" style={{color: 'red'}}>
{ touched ? error : '' }
</div>
</div>
);
const renderTextarea = ({ input, label, type, meta: {touched, invalid, error }}) => (
<div className={`form-group ${touched && invalid ? 'has-danger' : ''}`}>
<label>{label}</label>
<textarea className="form-control" {...input}/>
<div className="text-help" style={{color: 'red'}}>
{ touched ? error : '' }
</div>
</div>
);
class PostsNew extends Component{
componentDidMount(){
this.handleInitialize();
}
handleInitialize(){
const initData = {
"title": '',
"categories": '',
"content": ''
};
this.props.initialize(initData);
}
handleFormSubmit(formProps){
this.props.createPost(formProps)
}
render(){
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<h3>Create A New Post</h3>
<Field
label="Title"
name="title"
type="text"
component={renderInput} />
<Field
label="Categories"
name="categories"
type="text"
component={renderInput}
/>
<Field
label="Content"
name="content"
component={renderTextarea}
/>
<button type="submit" className="btn btn-primary" >Submit</button>
</form>
);
}
}
function validate(formProps){
const errors = {};
if(!formProps.title){
errors.title = 'Enter a username';
}
if(!formProps.categories){
errors.categories = 'Enter categories';
}
if(!formProps.content){
errors.content = 'Enter content';
}
return errors;
}
const form = reduxForm({
form: 'PostsNewForm',
validate
});
export default connect(null, { createPost })(form(PostsNew));
You need to connect form reducer to your combine reducers
form: formReducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import authReducer from './authReducer';
import productsReducer from './productsReducer';
export default combineReducers({
auth: authReducer,
form: formReducer,
products: productsReducer,
});

Resources