How to use withFormik Field with react-datepicker? - reactjs

I am trying to send my dob data from my Main class to a child component (RegisterAccount.jsx) and validate it at child component using yup and withFormik Field. The problem is that:
I could not make yup or formik/yup to validate dob field.
DatePicker doesn't show selected value inside the textbox after selected.
Please check my below code:
Here is my Main.jsx class
// Some imports were removed to keep everything looks cleaner
import RegisterAccount from "RegisterAccount.jsx";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
dob: ""
}
}
render() {
return (
<Container fluid>
<Switch>
<Route exact path="/" render={() => <RegisterAccount data={this.state.dob} />} />
</Switch>
</Container>
)
}
}
export default Main;
Here is my RegisterAccount.jsx
// Some imports were removed to keep everything looks cleaner
import { Form as FormikForm, Field, withFormik } from "formik";
import * as Yup from "yup";
import DatePicker from "react-datepicker";
const App = ({ props }) => (
<FormikForm className="register-form " action="">
<h3 className="register-title">Register</h3>
<Form.Group className="register-form-group">
<DatePicker
tag={Field}
selected={props.data.dob}
onChange={(e, val) => {
console.log(this);
this.value=e;
props.data.dob = e;
console.log(props.data.dob);
}}
value="01-01-2019"
className="w-100"
placeholderText="BIRTH DATE"
name="dob" />
{touched.username && errors.username && <p>{errors.username}</p>}
</Form.Group>
</FormikForm>
);
const FormikApp = withFormik({
mapPropsToValues({ data }) {
return {
dob: data.dob || ""
};
},
validationSchema: Yup.object().shape({
dob: Yup.date()
.max(new Date())
})(App);
export default FormikApp;

Use setFieldValue method from formik's injected props.
Define it on onChange handler for your inputsetFieldValue('dob','Your Value').
You can access it from
const MyForm = props => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => {
setFieldValue("name", "Your value"); // Access it from props
}}
onBlur={handleBlur}
value={values.name}
name="name"
/>
</form>
);
};
const MyEnhancedForm = withFormik({
// your code
})(MyForm)
Reference - https://jaredpalmer.com/formik/docs/api/formik#setfieldvalue-field-string-value-any-shouldvalidate-boolean-void

Related

how to validate inputs in a child component (formik)

I generated Field in CustomField and call it in the CustomForm component. I want to validate each Customefield in itself with validateAsync but validateAsync is not working and meta. the error object is always empty.
this is my CustomForm component:
import { Field, Form, Formik, yupToFormErrors } from "formik";
import React, { Component } from "react";
import CustomField from "./customeField";
import * as Yup from "yup";
class CustomForm extends Component {
state = {};
render() {
return (
<Formik
initialValues={{ name1: "", name2: "" }}
onSubmit={(values) => {
console.log(values);
}}
>
<Form>
<CustomField name="name1" lable="name1"></CustomField>
<CustomField name="name2" lable="name2"></CustomField>
<button type="submit">send</button>
</Form>
</Formik>
);
}
}
export default CustomForm;
and this is my CustomField component
import React, { Component } from "react";
import { Field, Form, Formik, useField, ErrorMessage } from "formik";
import * as Yup from "yup";
const CustomField = ({ lable, ...props }) => {
const [field, meta] = useField(props);
const validateAsync = () =>
Yup.object({
name1: Yup.string().required("error"),
});
return (
<div>
<label htmlFor={props.id || props.name}>{lable}</label>
<Field validate={validateAsync} {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : (
<div className="error"></div>
)}
</div>
);
};
export default CustomField;
there are two problems in your code, the first one is how you use the Yup for validation and the second problem is about that validateAsync function, as Formik doc mentions you have to get input value from the first argument and as result, you can return undefined (which means there is no error) or a string (error message), BTW it's also possible to return a promise that indicates is input value valid or not.
here is how you can go with this case:
const CustomField = ({ label, name }: CustomFieldProps) => {
const [field, meta] = useField({ name });
const validate = (x: string) => {
const error_msg = "Error!!!";
try {
Yup.string().required(error_msg).validateSync(x).toString();
} catch (error) {
return error_msg;
}
};
return (
<div>
<Field validate={validate} {...field} />
{meta.touched && meta.error && <div className="error">{meta.error}</div>}
</div>
);
};
p.s: here is the link of a working sample sandbox if you need a playground:
https://codesandbox.io/s/formik-field-validate-rx3snh?file=/src/App.tsx:173-650

How to validate and display errors using React Smart Form Component

Can't understand how to validate and render error massages in my FormInput component (below of input tag).
Advice please.
No understanding of error output.`
SmartForm
import React from "react";
import { useForm, get } from "react-hook-form";
export default function SmartForm({
defaultValues,
register,
children,
...rest
}) {
const props = { ...rest };
const methods = useForm(defaultValues);
const {
handleSubmit,
formState: { errors },
} = methods;
return (
<>
<form onSubmit={handleSubmit}>
{React.Children.map(children, (child) => {
return child.props.name
? React.createElement(child.type, {
...{
...child.props,
register: methods.register,
key: child.props.name,
},
})
: child;
})}
{Object.values(errors)}
<input type="submit"></input>
</form>
</>
);
}
`
FormInput
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
LoginForm
import React from "react";
import SmartForm from "../SmartModalForm/smartModalForm";
import FormInput from "./FormInput/formInput";
export default function LoginForm(props) {
const onSubmit = (e) => {
e.preventDefault();
console.log(e);
};
return (
<SmartForm
handleClose={handleClose}
handleSubmit={onSubmit}
>
<FormInput
name="email"
rules={{ required: "This is required." }}
/>
<FormInput
name="password"
rules={{ required: "This is required." }}
/>
</SmartForm>
);
}
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
In SmartFormComponent you are just passing register from useFormReturn and not all methods. So, in FormInput, error is undefined.
Update React.createElement in SmartFormComponent as below
React.createElement(child.type, {
...{
...child.props,
methods, // not just register, but pass entire `useFormReturn` values
key: child.props.name,
},
})

How to change Form Input Type dynamically to use child components with react-hook-form

I am not able to use a basic component in multiple form because each form in react-hook-form requires its custom type input.
App.tsx -
import { useForm } from "react-hook-form";
export type FormValues = {
FirstName: string;
};
export default function App() {
const { handleSubmit, control } = useForm<FormValues>({
defaultValues: {
FirstName: ""
},
mode: "onChange"
});
const onSubmit = (data: FormValues) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input control={control} name="FirstName" rules={{ required: true }} />
<input type="submit" />
</form>
);
}
Input.tsx -
import { useController, UseControllerProps } from "react-hook-form";
import { FormValue } from './App.tsx';
function Input(props: UseControllerProps<FormValues>) {
const { field, fieldState } = useController(props);
return (
<div>
<input {...field} placeholder={props.name} />
<p>{fieldState.isTouched && "Touched"}</p>
<p>{fieldState.isDirty && "Dirty"}</p>
<p>{fieldState.invalid ? "invalid" : "valid"}</p>
</div>
);
}
The FormValues type limits the input component to one form and cannot be used with other forms in which FormValues may change.
Is there any way to resolve this issue?

How to transform inheritance into composition in React (example)?

I have two forms: <Login /> and <Register />. Both have username and password and the register form also has a name field. Also there is a login button and a register button. The common code they share though is just the username and password so I want to refactor my code to have a component <UsernamePassord /> and use it in both <Login /> and <Register />.
What I have done so far (pseudocode):
class UsernamePassword extends React {
constructor() {
this.state = {username: "", password: ""}
}
render() {
return <div>
<input username onChange => setState(username: e.value)
<input password onChange => setState(password: e.value)
</div>
}
class Login extends UsernamePassword {
render() {
return <>
{ super.render() }
<button onClick => this.state.username && this.state.password && signIn() />
}
class Register extends UsernamePassword {
constructor() {
this.state = {
...this.state,
name: ""
}
}
render() {
return <>
<input name onChange => setState(name: e.value)
{ super.render() }
<button onClick => this.state.username && this.state.password && this.state.name && signUp() />
}
I don't like this code at all. It seems really difficult to scale. How can this be done cleaner using composition ?
There are a number of ways to handle this. You could create a component that accepts props (in this case, username and password) and handles the manipulation of values. It subsequently fires an onChange event of some sort that notifies the parent of the change and the parent can manage the state. Alternatively, you could manage the state in the component and just create an event handler prop for alerting parent components of the state. I made a working example of the second scenario based off of your code (changing class-based components for functional ones for simplicity):
import React, { useState } from "react";
import "./styles.css";
const UsernamePassword = ({ onChange }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleChange = () => onChange({ username, password });
return (
<div>
<input value={username} onChange={(e) => setUsername(e.target.value)} />
<input value={password} onChange={(e) => setPassword(e.target.value)} />
<button onClick={handleChange}>Click me!</button>
</div>
);
};
const Login = () => {
const onChange = (value) => console.log("Login", value);
return (
<>
<h2>Login</h2>
<UsernamePassword onChange={onChange} />
</>
);
};
const Register = () => {
const onChange = (value) => console.log("Register", value);
return (
<>
<h2>Register</h2>
<UsernamePassword onChange={onChange} />
</>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Register />
<Login />
</div>
);
}

How to fix component display overlap

I am aiming to display the component "SignIn" when I select the sign in button in the landing page. However, the component in the landing page is still displayed after selecting the button. Here are some images illustrating this problem.
Before Click: enter image description here
After Click: enter image description here
The issue is the second image. What could be the biggest cause of it?
Input is greatly appreciated.
Code - Home Page (LandingPage):
import React from 'react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { Link } from 'react-router-dom';
import * as ROUTES from '../../routes.jsx';
import './LandingPage.css'
export default class LandingPage extends React.Component {
render() {
return(
<div>
<Button className="signInButton" variant="light"><Link to={ROUTES.SIGN_IN}>Sign In</Link></Button>
<h1 className="landingBanner" >Streamosphere</h1>
<SignUpFormBase/>
</div>
);
}
}
const INITIAL_STATE = {
email: '',
password: '',
error: null,
};
export default class SignUpFormBase extends React.Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
}
onSubmit = event => {
console.log("Props");
console.log(this.props);
const { history } = this.props;
const { email, password } = this.state;
this.props.firebase
.doCreateUserWithEmailAndPassword(email, password)
.then(authUser => {
this.setState({ ...INITIAL_STATE });
})
.catch(error => {
this.setState({ error });
});
event.preventDefault();
};
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const {
email,
password,
error,
} = this.state;
const isInvalid = password === '' || email === '';
return (
<Form className="signUpForm"
onSubmit={this.onSubmit}>
<h1 className="signUpBanner">Sign Up </h1>
<Form.Group controlId="formSignUpEmail">
<Form.Label>Email address</Form.Label>
<Form.Control name="email"
value={email}
onChange={this.onChange}
type="email"
placeholder="Enter email" />
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
<Form.Group controlId="formSignUpPassword">
<Form.Label>Password</Form.Label>
<Form.Control name="password"
value={password}
onChange={this.onChange}
type="password"
placeholder="Password" />
</Form.Group>
<Button type="submit"
disabled={isInvalid}
className="signUpButton"
variant="light" >
Sign Up
</Button>
{error && <p>{error.message}</p>}
</Form>
);
}
}
const SignUpLink = () => (
<p>
Already have an account? <Link to={ROUTES.SIGN_IN}>Sign In</Link>
</p>
);
Sign In (file w/ SignIn Component):
import React, { Component } from 'react';
import SignUpLink from './LandingPage.jsx'
import * as ROUTES from '../../routes.jsx';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import './LandingPage.css'
export default class SignInPage extends Component {
render() {
return(
<div>
<h1 className="landingBanner" >Streamosphere</h1>
<SignInFormBase/>
<SignUpLink />
</div>
);
}
}
const INITIAL_STATE = {
email: '',
password: '',
error: null,
};
export default class SignInFormBase extends Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
}
onSubmit = event => {
const { email, password } = this.state;
this.props.firebase
.doSignInWithEmailAndPassword(email, password)
.then(() => {
this.setState({ ...INITIAL_STATE });
this.props.history.push(ROUTES.HOME);
})
.catch(error => {
this.setState({ error });
});
event.preventDefault();
};
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { email, password, error } = this.state;
const isInvalid = password === '' || email === '';
return (
<Form className="signUpForm"
onSubmit={this.onSubmit}>
<h1 className="signUpBanner"> Sign In </h1>
<Form.Group controlId="formSignInEmail">
<Form.Label>Email address</Form.Label>
<Form.Control name="email"
value={email}
onChange={this.onChange}
type="email"
placeholder="Enter email" />
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
<Form.Group controlId="formSignInPassword">
<Form.Label>Password</Form.Label>
<Form.Control name="password"
value={password}
onChange={this.onChange}
type="password"
placeholder="Password" />
</Form.Group>
<Button type="submit"
disabled={isInvalid}
className="signUpButton"
variant="light" >
Sign In
</Button>
{error && <p>{error.message}</p>}
</Form>
);
}
}
URL Routes (routes.jsx):
export const LANDING = '/';
export const SIGN_IN = '/signin';
export const HOME = '/home';
export const ACCOUNT = '/account';
App.jsx (entry point, where LandingPage is the 1st page rendered):
import { Route } from 'react-router'
import React from 'react';
import LandingPage from './components/containers/LandingPage'
import SignInPage from './components/containers/SignIn'
import HomePage from './components/containers/HomePage'
import * as ROUTES from './routes.jsx';
class App extends React.Component {
render() {
return (
<div>
<Route exact path={ROUTES.LANDING} component={LandingPage} />
<Route path={ROUTES.HOME} component={HomePage} />
<Route path={ROUTES.SIGN_IN} component={SignInPage} />
</div>
);
}
}
export default App;
It is because of the Link from react-router-dom.
You could circumvent it by reloading the whole page.
For example, use <a href="/sign-in></a> instead.
You need to wrap all routes in App with Switch which would render only one matching child component.
// App.jsx
import { Route, Switch } from 'react-router'
import React from 'react';
import LandingPage from './components/containers/LandingPage'
import SignInPage from './components/containers/SignIn'
import HomePage from './components/containers/HomePage'
import * as ROUTES from './routes.jsx';
class App extends React.Component {
render() {
return (
<Switch>
<Route exact path={ROUTES.LANDING} component={LandingPage} />
<Route path={ROUTES.HOME} component={HomePage} />
<Route path={ROUTES.SIGN_IN} component={SignInPage} />
</Switch>
);
}
}
export default App;

Resources