save data from many forms using getFieldValue() - javascript

I try to save data from 2 form (Main and SubForm) using getFieldValue(). Here should appear both form data:
const save = () => {
console.log(myRef.current.getFieldValue());
};
Now i get an empty object when i click save handler. If i remove the <SubForm/> i get value from Main form, but if i add again the second form i ca not get data from both form.
,br> How to get data, clicking on save button, from both forms using getFieldValue()?
demo: https://codesandbox.io/s/dazzling-ganguly-vz7o7?file=/src/OuterForm.js
A part of my code:
<Form
name="main"
ref={myRef}
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
id="ma"
form={form}
>
<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: "Please input your username!" }]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: "Please input your password!" }]}
>
<Input.Password />
</Form.Item>
</Form>
<SubForm myRef={myRef} />
<button onClick={save}>save</button>
</div>

You need to define 2 refs, one for each form.
const mainFormRef = useRef();
const subFormRef = useRef();
and set these refs on respective forms
<Form
name="main"
ref={mainFormRef}
....
>
....
</Form>
<SubForm myRef={subFormRef} />
finally, inside save function, call getFieldValue on both refs
const save = () => {
console.log(mainFormRef.current.getFieldValue());
console.log(subFormRef.current.getFieldValue());
};

Related

Antd with formik setFieldValue

There is a Antd table with data, when i click to button "Change", the entire line with data is passed to the function in the form of an object, now it remains to insert data into the fields, but I can't, what should I do?
How set data to antd input using outside function?
I can do it with .map (), but it seems more appropriate to me to use formik
const BankForm = (props) => {
const [form] = Form.useForm();
//show close form
const columns = [
...MY columns
{
title: "Действие",
key: "action",
fixed: "right",
render: (text, record) => (
<Space size="middle"
I clink to Button and all data of row go to ChangeBank(record)
<a onClick={() => ChangeBank(record)}> Изменить</a>
</Space>
),
},
];
const formik = useFormik({
initialValues: {
name_bank: "",
},
validate,
onSubmit: (values) => {
},
});
//my function to set data to form
let ChangeBank = (record) => {
formik.setFieldValue("name_bank", record.name_bank);
};
return (
<div className={clas.tableBlock_header}>
my form
<Form
form={form}
layout="vertical"
hideRequiredMark
onFinish={formik.handleSubmit}
>
<Form.Item
name="name_bank"
label="Название банка"
rules={[{ required: true, message: "Введите название банка" }]}
>
<Input value={formik.values.name_bank} name="name_bank" />
</Form.Item>
</Form>
);
};
If you want to set values in antd form fields with formik, dont give the name Form.item
AND if you want use antd with formik, and you need validaite your inputs just download Yup. Dont use antd menthods.
Yup with formik
<Form
form={form}
layout="vertical"
hideRequiredMark
onFinish={formik.handleSubmit}
>
<Form.Item
label="Название банка"
rules={[{ required: true, message: "Введите название банка" }]}
>
<Input value={formik.values.name_bank} name="name_bank" />
</Form.Item>
</Form>**strong text**

React Context Updates Not Firing During OnSubmit Functions?

I am using the Context API to add user details from a form to a global state. When I submit the form, the state is always "one step behind" - essentially, a double click is required to get the desired result.
Basic recreation of the code (have removed irrelevant bits and some imports):
import { UserProvider, UserContext } from "../../StateContext"
export default function SignUp() {
const user = useContext(UserContext)
const history = useHistory()
const handleSubmit = (e) => {
e.preventDefault()
user.setName(userDetails.name)
//logging out the user object here will result in the previous values shown
}
const [userDetails, setUserDetails] = useState({
name: null,
age: null,
})
return (
<>
<form onSubmit={handleSubmit}>
<div className="form-vertical-batch">
<FormControl>
<Input
type="text"
placeholder="Your Name"
required={true}
onChange={(e) =>
setUserDetails({ ...userDetails, name: e.target.value })
}
></Input>
<FormHelperText>
Put the name you're best known by online - either a nickname,
brand name, or your real name.
</FormHelperText>
</FormControl>
<FormControl>
<TextField
type="number"
inputProps={{ min: 18, max: 99 }}
onChange={(e) =>
setUserDetails({ ...userDetails, age: e.target.value })
}
/>
<FormHelperText>
You currently must be at least 18 years old to use the platform.
</FormHelperText>
</FormControl>
</div>
</div>
<input
id="formButton"
className="btn sign-up-button"
type="submit"
placeholder="Send message"
/>
</form>
</>
)
}
To clarify the issue here - if I submit with a name as "Reikon" and log our the user object, the first time it will return as null, and then the second time it will return "Reikon" as expected.

Next.js and react-hook-form - ref does not work [duplicate]

I'm trying to do validations for my form in react. I chose "react-hook-form" library. But I'm constantly getting error "Path.split is not a function. Even after using the default example given in their website, I'm getting the same error.
This is the default code given in the official site.
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example")); // watch input value by passing the name of it
return (
{/* "handleSubmit" will validate your inputs before invoking "onSubmit" */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input name="example" defaultValue="test" ref={register} />
{/* include validation with required or other standard HTML validation rules */}
<input name="exampleRequired" ref={register({ required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
react-hook-form updated to 7.0.0 from 6.X.X and has breaking changes:
You have to replace all ref={register} with {...register('value_name')}
Example:
Version 6.X.X:
<input ref={register({ required: true })} name="test" />
Version 7.0.X:
<input {...register('test', { required: true })} />
Simple input with required and errors.message features, necessary changes in update:
From version 6.x.x:
function MyComponent(props) {
const { register, handleSubmit, errors } = useForm();
const onSubmit = (values) => {...};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="message"
autoComplete="off"
ref={register({
required: "Required",
})}
/>
{errors.message && errors.message.message}
<input type="submit" />
</form>
</div>
);
}
To version 7.x.x:
function MyComponent(props) {
const { register, handleSubmit, formState: { errors }} = useForm();
const onSubmit = (values) => {...};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="message"
autoComplete="off"
{...register("message", {
required: "Required",
})}
/>
{errors.message && errors.message.message}
<input type="submit" />
</form>
</div>
);
}
In addition to register fix, if you use errors from useForm(), now errors feature is exported from formState.
Worth mentioning that if you are using material ui or something similar, where ref={ref} throws an error (because it expects a different prop name instead of ref), you might want to
import { TextField } from '#material-ui/core';
return (
<TextField {...register('name')} />
)
there is a migration guide for this here, but still worth to mention
As noted above, there are changes to how register is to be used in v7
If you are still getting errors, ensure that id is actually a string and not any other type such as a number.
<input {...register(id)} />
import { useForm } from "react-hook-form";
export default function App() {
const { register, formState: { errors }, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true })} />
{errors.firstName?.type === 'required' && "First name is required"}
<input {...register("lastName", { required: true })} />
{errors.lastName && "Last name is required"}
<input type="submit" />
</form>
);
}

How to give validation in multi step form using react

I am working on a scenario where I have to do a multi-step form which I have already done, as well as the validation part. I am using react-hook-form for validation.
I have multi-step form:
in the first form I have several fields and one radio button
by default radio button is ticked on for auto generated pass so in this case I have nothing to do
the second one is let me create a password so in this case one input field will be show and the user will create the password
Issue
In my final form I am doing the validation like below:
{
fields: ["uname", "email", "password"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
So to validate uname, email and password I am passing the values like above.
But when the radio button is ticked for auto generated password it is still handling the validation, I click on next and it is not going to next for because of password field.
And if I check the radio button as let me create the password it goes to next form and when I came back by clicking back it is going to auto generated password again and it is not holding the previous state. For other input fields it is handling the previous values but not in case of radio button scenario.
My full working code sandbox
Answer 1 The reason is you fields: ["uname", "email", "password"] is fixed, password is always to be taken validation.
Solution Need to store state of Form1 in App so you can check if the state of auto generated password is on remove password from the list
App.js
... other code
// need to move state and function form Form to app
const [show_input, setshow_input] = useState(false);
const createInput = () => {
setshow_input(true);
};
const auto_text = () => {
setshow_input(false);
};
const forms = [
{
// validate based on show_input state
fields: show_input ? ["uname", "email", "password"] : ["uname", "email"], //to support multiple fields form
component: (register, errors, defaultValues) => (
<Form1
register={register}
errors={errors}
defaultValues={defaultValues}
auto_text={auto_text}
createInput={createInput}
show_input={show_input}
/>
)
},
{
fields: ["lname"],
component: (register, errors, defaultValues) => (
<Form2
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
},
{
component: (register, errors, defaultValues) => (
<Form3
register={register}
errors={errors}
defaultValues={defaultValues}
/>
)
}
];
... other code
Answer 2 When you go next the Form1 is unmounted so its state is destroyed. When you store Form1's state in App.js you will fix this issue too
Bonus: It's prefered to use camalCase (E.g: showInput) rather than underscore (show_input)
The main problem is that you render the forms conditionally so all the previous form values are removed. The solution for this is to keep all forms mounted and just use display: none or display: block depending on which form is selected. This way all values will be persisted whenever you go to next or prev form or submit the form.
The second problem that you didn't remove the password field when it's unmounted so when moveToNext is called the valid argument in triggerValidation callback is always false. I fixed that by setting the fields for Form1 conditionally depending on if the password input is visible or not.
The third problem you are using defaultValues for the wrong purpose. You can get the current form values using getValues() which will return all the current values of the form.
I set the default value for uname field just as an example to show you how defaultValues should be used.
you can check the full solution here: https://codesandbox.io/s/fragrant-forest-75pzs?file=/src/App.js
here are all the changed files:
App.js
import React, { useState } from "react";
import Form1 from "./components/Form1";
import Form2 from "./components/Form2";
import Form3 from "./components/Form3";
import { useForm } from "react-hook-form";
function MainComponent() {
const {
register,
triggerValidation,
defaultValues,
errors,
getValues
} = useForm({
// You can set default values here
defaultValues: {
uname: "Lol"
}
});
console.log("Errors: ", errors);
const [currentForm, setCurrentForm] = useState(0);
// control password input visibility and Form1 fields
const [passwordVisible, setPasswordVisible] = useState(false);
const showPassword = () => {
setPasswordVisible(true);
};
const hidePassword = () => {
setPasswordVisible(false);
};
const forms = [
{
fields: passwordVisible
? ["uname", "email", "password"]
: ["uname", "email"],
component: (register, errors) => (
<Form1
// a key is needed to render a list
key={0}
// this will be used to set the css display property to block or none on each form
shouldDisplay={currentForm === 0}
register={register}
errors={errors}
showPassword={showPassword}
hidePassword={hidePassword}
passwordVisible={passwordVisible}
/>
)
},
{
fields: ["lname"],
component: (register, errors) => (
<Form2
key={1}
shouldDisplay={currentForm === 1}
register={register}
errors={errors}
/>
)
},
{
component: (register, errors) => (
<Form3
key={2}
shouldDisplay={currentForm === 2}
register={register}
errors={errors}
values={getValues()}
/>
)
}
];
const moveToPrevious = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm - 1);
});
};
const moveToNext = () => {
triggerValidation(forms[currentForm].fields).then(valid => {
if (valid) setCurrentForm(currentForm + 1);
});
};
const prevButton = currentForm !== 0;
const nextButton = currentForm !== forms.length - 1;
const handleSubmit = e => {
console.log("whole form data - ", getValues());
};
return (
<div>
<div className="progress">
<div>{currentForm}</div>
</div>
{forms.map(form => form.component(register, errors))}
{prevButton && (
<button
className="btn btn-primary"
type="button"
onClick={moveToPrevious}
>
back
</button>
)}
{nextButton && (
<button className="btn btn-primary" type="button" onClick={moveToNext}>
next
</button>
)}
{currentForm === 2 && (
<button
onClick={handleSubmit}
className="btn btn-primary"
type="submit"
>
Submit
</button>
)}
</div>
);
}
export default MainComponent;
Form1
import React from "react";
function Form1({
register,
errors,
shouldDisplay,
passwordVisible,
showPassword,
hidePassword
}) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User name</label>
<input type="text" name="uname" ref={register({ required: true })} />
{errors.uname && <span>required</span>}
<label>Email</label>
<input type="email" name="email" ref={register({ required: true })} />
{errors.email && <span>required</span>}
</div>
<div>
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<label className="form_label">Password</label>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios1"
value="auto_pass"
className="form-check-input"
defaultChecked={true}
onChange={hidePassword}
/>
Auto generated password
</label>
</div>
<div className="form-check">
<label>
<input
type="radio"
name="auto_pass"
id="Radios2"
value="let_me"
className="form-check-input"
onChange={showPassword}
/>
Let me create the password
</label>
</div>
</div>
{passwordVisible && (
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mb-3">
<label className="form_label">Password</label>
<input
type="password"
name="password"
className="form-control"
ref={register({ required: true })}
/>
{errors.password && (
<span className="text-danger">Password is reguired</span>
)}
</div>
)}
</div>
</form>
</div>
);
}
export default Form1;
Form2
import React from "react";
function Form2({ register, errors, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<form autoComplete="on">
<br />
<div className="form-group">
<label>User last name</label>
<input type="text" name="lname" ref={register({ required: true })} />
{errors.lname && <span>required</span>}
</div>
</form>
</div>
);
}
export default Form2;
Form3
import React from "react";
function Form3({ values, shouldDisplay }) {
return (
<div style={{ display: shouldDisplay ? "block" : "none" }}>
<h3>Want to display all values here like below</h3>
{Object.entries(values).map(([key, value]) => (
<p key={key}>
{key}: {value}
</p>
))}
<br />
<p>So that use can check for any Wrong info</p>
</div>
);
}
export default Form3;

multiple forms in one page - reactjs

I am building page where user can switch between login and signup mode by clicking the switch button.
Login form has 2 input fields and signup form has 3 input fields. My thinking was to build 2 separate forms independent from each other and use 2 separate custom hook instances.
import React, { useState } from "react";
import { useForm } from "../../shared/hooks/form-hook";
import Card from "../../shared/components/UIElements/Card";
import Input from "../../shared/components/FormElements/Input";
import Button from "../../shared/components/FormElements/Button";
import {
VALIDATOR_MINLENGTH,
VALIDATOR_EMAIL
} from "../../shared/util/validators";
import "./Auth.css";
const Auth = props => {
const [showLogin, setShowLogin] = useState(true);
const [formStateLogin, inputHandlerLogin] = useForm(
{
email: {
value: "",
isValid: false
},
password: {
value: "",
isValid: false
}
},
false
);
const [formStateSignup, inputHandlerSignup] = useForm(
{
name: {
value: "",
isValid: false
},
email: {
value: "",
isValid: false
},
password: {
value: "",
isValid: false
}
},
false
);
const loginSubmitHandler = event => {
event.preventDefault();
console.log("login handler");
};
const signupSubmitHandler = event => {
event.preventDefault();
console.log(formStateSignup.inputs);
};
const switchButtonHandler = () => {
setShowLogin(!showLogin);
};
return (
<Card className="authentication">
{showLogin ? (
<form onSubmit={loginSubmitHandler} className="place-form">
<h2>Enter your login details</h2>
<Input
id="email"
element="input"
type="email"
placeholder="Email address"
label="Email"
validators={[VALIDATOR_EMAIL(), VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerLogin}
errorText="Please enter valid email address"
/>
<Input
id="password"
element="input"
type="password"
placeholder="Password"
label="Password"
validators={[VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerLogin}
errorText="Please enter valid password (at least 5 chars)"
/>
<Button type="submit" disabled={!formStateLogin.isValid}>
LOGIN
</Button>
</form>
) : (
<form onSubmit={signupSubmitHandler} className="place-form">
<h2>Enter your signup details</h2>
<Input
id="name_s"
element="input"
type="text"
placeholder="Enter your name"
label="Name"
validators={[VALIDATOR_MINLENGTH(2)]}
onInput={inputHandlerSignup}
errorText="Please enter valid name at least 2 chars"
/>
<Input
id="email_s"
element="input"
type="email"
placeholder="Email address"
label="Email"
validators={[VALIDATOR_EMAIL(), VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerSignup}
errorText="Please enter valid email address"
/>
<Input
id="password_s"
element="input"
type="password"
placeholder="Password"
label="Password"
validators={[VALIDATOR_MINLENGTH(5)]}
onInput={inputHandlerSignup}
errorText="Please enter valid password (at least 5 chars)"
/>
<Button type="submit" disabled={!formStateSignup.isValid}>
LOGIN
</Button>
</form>
)}
<Button inverse onClick={switchButtonHandler}>
{showLogin ? "SWITCH TO SIGNUP" : "SWITCH TO LOGIN"}
</Button>
</Card>
);
};
export default Auth;
Both forms seem to render fine but the trouble is when I enter text in one form and decide to switch to other form, values from departed form are not lost but rather translated to new form:
Is this limitation of ReactJS, is it HTML? :) Or is it just my buggy code?
It's not a good convention to have two forms in one component, it makes a mess...I would make rather two separated components of LoginForm and SignUpForm and switch between them through ternary operator based on the state whatever way you like. Your forms and their state will be separated and code is more readable
It happens same for checkbox and radio buttons if you select radio button on first page then renders second page with radio button it will be automatically selected as dom operations are costly,
In your case react is just adding new third field and removing it you need to set the value attribute of fields to respective state.
you can actually. use the nested route to switch between components
import { Switch, Route } from 'react-router-dom';
<Switch>
<Route path='/register' >
// component 1
<Register />
</Route>
<Route path='/login' >
// component 2
<Login />
</Route>
</Switch>

Categories

Resources