I have a project on ReactJS which you can find here (see develop-branch) or check it out on our web site.
As you can see, I use formik to handle forms.
Now I have only one submit button which handles all forms however it does not link with forms by form attribute. It was OK.
Unfortunately, I've faced a problem when having a go to implement form validation. I still prefer using formik validation, but the thing is that it demands a direct connection between form and submit button like this:
export function GenerateButton(props) {
return (
<Button id="genButton"
form="form1"
type="submit"
onClick={props.onClick}>
Generate
</Button>
);
}
Any ideas how I can link all forms with submit button?
Or I have to just use fictitious buttons in every form (position: absolute; left: -9999px;) and imitate their click after pushing generate button?
P.S. now there is id="forms" in html form tag, it is just stupid mistake, must be class attribute. I can generate unique id this way: id={"form"+(props.index + 1)}.
P.S.S. I am so sorry for my English.
I think you can handle this with fieldarray of Formik very easily.
you will have an array and a list of forms in it then you can simply add and remove forms.
you won't have any problem with using validation of Formik too.
here is an example that exactly does what you want:
import React from "react";
import { Formik, Form, Field, FieldArray } from "formik";
const formInitialValues = { name: "", lastName: "" };
const FormList = () => (
<Formik
initialValues={{ forms: [formInitialValues] }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<FieldArray
name="forms"
render={arrayHelpers => (
<div>
{values.forms.map((formItem, index) => (
<div key={index}>
<Field name={`forms.${index}.name`} />
<Field name={`forms.${index}.lastName`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a form from the list of forms
>
-
</button>
<button
type="button"
onClick={() =>
arrayHelpers.insert(index, formInitialValues)
} // insert an empty string at a position
>
+
</button>
</div>
))}
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
);
export default FormList;
I have provided a code sandbox version for you too
please let me know if you still have any problem
more reading:
https://jaredpalmer.com/formik/docs/api/fieldarray#fieldarray-array-of-objects
The fieldArray solution looks quite complex and I think useFormik is a better option here.
For example:
import React from 'react'
import { Button, Grid, TextField } from '#mui/material'
import clsx from 'clsx'
import { useFormik } from 'formik'
export function MyFormComponent(props: any) {
const formA = useFormik({
initialValues: {
age: 23
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formB = useFormik({
initialValues: {
name: 'bar'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formC = useFormik({
initialValues: {
description: 'A flat object'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const submitForm = () => {
//get your values here
const values1 = formA.values
const values2 = formB.values
const values3 = formC.values
//data access for storing the values
}
return (
<div>
<form onSubmit={formA.handleSubmit}>
<TextField
style={{ marginBottom: 20 }}
fullWidth
type='number'
label='Age'
id='age'
name='age'
InputProps={{
inputProps: {
min: 0
}
}}
onChange={formA.handleChange}
value={formA.values.age}
/>
</form>
<form onSubmit={formB.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formB.handleChange}
value={formB.values.name}
style={{ marginRight: 20 }}
fullWidth
name='name'
type='text'
label='Name'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<form onSubmit={formC.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formC.handleChange}
value={formC.values.description}
style={{ marginRight: 20 }}
fullWidth
name='description'
type='text'
label='Description'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<Button color='secondary' variant='contained' onClick={submitForm}>
Submit
</Button>
</div>
)
}
I'm created a special utility library to work with multiple forms (including repeatable forms) from one place. I see that you are using Formik component (instead of useFormik hook), but maybe it still be useful.
Check this example
https://stackblitz.com/edit/multi-formik-hook-basic-example?file=App.tsx
You can find the library here
Related
I build a simple form using formik. I have a situation that one field is hidden after I choose in radio buttun 'no'.
But if I enter value to this field and then choose 'no' to hide it, and submit the form I will get in the final json also the value of the hidden field.
My expectation is when field isn't show to the screen, remove it from the final json.
My form:
import React from "react";
import { Formik } from "formik";
import { Form } from "react-bootstrap";
import { Radio } from "antd";
const CustomFormik = () => {
return (
<>
<Formik
initialValues={{
input1: null,
radio: null,
input2: null
}}
onSubmit={(values) => {
console.log(values);
}}
>
{(formik) => (
<>
<Form
onSubmit={formik.handleSubmit}
className="custom-formik-container"
>
<Form.Group>
<span>input1: </span>
<Form.Control
id={"input1"}
name={"input1"}
{...formik.getFieldProps("input1")}
/>
</Form.Group>
{formik.getFieldProps("radio").value !== 2 && (
<Form.Group>
<span>input2: </span>
<Form.Control
id={"input2"}
name={"input2"}
{...formik.getFieldProps("input2")}
/>
</Form.Group>
)}
<Form.Group>
<span>radio: </span>
<Radio.Group {...formik.getFieldProps("radio")}>
<Radio value={1}>yes</Radio>
<Radio value={2}>no</Radio>
</Radio.Group>
</Form.Group>
</Form>
<button
style={{
width: 200,
height: 30,
background: "#113776",
color: "#fff"
}}
onClick={() => formik.handleSubmit()}
>
S U B M I T
</button>
</>
)}
</Formik>
</>
);
};
export default CustomFormik;
For example enter:
'abc' to the first input.
'efg' to the second input.
'no' to the radio button.
The final json in console.log will be:
{input1: "abc",input2: "efg", radio: 2}
But my expectation in to be:
{input1: "abc", radio: 2}
codesandbox
Thank you guys!
I don't know if this fits your needs but I had a similar case in my project using redux-form and what i did was to delete the property before submiting it
sth like this
onSubmit={(values) => {
if (values.radio === 2) {
let clonedValues = { ...values };
delete clonedValues.input2;
console.log(clonedValues);
} else {
console.log(values);
}
}}
this will do the job.
onSubmit={(values) => {
if(values.radio === 2){
delete values['input2'];
}
console.log(values);
}}
import React, { useState } from "react";
import FileBase64 from "react-file-base64";
import { useDispatch } from "react-redux";
import { makeStyles } from "#material-ui/core/styles";
import { TextField, Select, Input, MenuItem, Button } from "#material-ui/core";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
import { updatePost } from "../actions/post";
const useStyles = makeStyles((theme) => ({
textField: {
marginBottom: theme.spacing(2),
},
buttons: {
marginTop: theme.spacing(2),
},
}));
const tags = ["fun", "programming", "health", "science"];
const postSchema = yup.object().shape({
title: yup.string().required(),
subtitle: yup.string().required(),
content: yup.string().min(20).required(),
tag: yup.mixed().oneOf(tags),
});
const EditPostForm = ({ history, post, closeEditMode }) => {
const dispatch = useDispatch();
const [file, setFile] = useState(post?.image);
const { register, handleSubmit, control, errors, reset } = useForm({
resolver: yupResolver(postSchema),
});
const onSubmit = (data) => {
const updatedPost = {
_id: post._id,
...data,
image: file,
};
dispatch(updatePost(post._id, updatedPost));
reset();
setFile(null);
closeEditMode();
};
const classes = useStyles();
return (
<div>
<form noValidate autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
<TextField
id="title"
label="Başlık"
name="title"
variant="outlined"
className={classes.textField}
size="small"
{...register('title')}
error={errors?.title ? true : false}
fullWidth
defaultValue={post?.title}
/>
<TextField
id="subtitle"
label="Alt Başlık"
name="subtitle"
variant="outlined"
className={classes.textField}
size="small"
{...register('subtitle')}
error={errors?.subtitle ? true : false}
fullWidth
defaultValue={post?.subtitle}
/>
<Controller
render={({field}) => (
<Select
{...field}
input={<Input />}
className={classes.textField}
fullWidth
>
{
tags.map((tag, index) => (
<MenuItem {...field} key={index} value={tag}>
{tag}
</MenuItem>
))
}
</Select>
)}
name='tag'
control={control}
error={errors?.tag ? true : false}
defaultValue={tags[0]}
/>
<TextField
id="content"
label="İçerik"
name="content"
multiline
size="small"
{...register('content')}
rows={16}
className={classes.textField}
variant="outlined"
error={errors?.content ? true : false}
fullWidth
defaultValue={post?.content}
/>
<FileBase64 multiple={false} onDone={({ base64 }) => setFile(base64)} />
<div className={classes.buttons}>
<Button color="primary" variant="outlined" onClick={closeEditMode}>
Vazgeç
</Button>{" "}
<Button color="secondary" variant="outlined" type="submit" >
Kaydet
</Button>
</div>
</form>
</div>
);
};
export default EditPostForm;
I have EditPostForm component, component doesn't give any error but when I tried to submit my form onSubmit function is not triggered.
I used react-hook-form to create my form and I used material UI components inside form.
When I Click button which has type submit does not trigger onSubmit function which is called inside of handleSubmit. Why onSubmit is not triggered?
onSubmit isn't triggered because you may have form errors
You can get errors from formState object (const { formState } = useForm(...))
And then use error={formState.errors?.content ? true : false} in your code
https://react-hook-form.com/api/useform/formstate
See an example here
https://codesandbox.io/s/keen-burnell-2yufj?file=/src/App.js
You need to pass onSubmit and onError both.
Like this:
onPress={handleSubmit(onSubmit, onErrors)}
I faced the same error, the problem was that, my register were Boolean and my input was string, and since the value was not required It didn't show errors until I figure out the problem and change register from Boolean to string
I am using react-hook-form V7 with Material UI to create a simple form however, there is a weird behavior when setting the mode in useForm({mode: ".."})
Here is my code snippet:
import { yupResolver } from "#hookform/resolvers/yup";
import { Button, Grid, Link, TextField, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Alert from "#material-ui/lab/Alert";
import { Controller, useForm } from "react-hook-form";
import { _MESSAGES } from "src/constants/messages";
import * as yup from "yup";
import AuthContainer from "./AuthContainer";
// ##################### Helpers ######################
/**
* #param {object} error
* #param {string} msg
*/
const renderError = (error, msg) =>
error ? <Alert severity="error">{msg}</Alert> : "";
// ############## Schema & Default Values ##############
const schema = yup.object().shape({
email: yup.string().required(),
password: yup.string().required(),
});
const defaultValues = {
email: "",
password: "",
};
// ###################### Styles ######################
const useStyles = makeStyles((theme) => ({
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
// ################# Main Component ###################
const SignInPage = () => {
const classes = useStyles();
const {
handleSubmit,
formState: { errors },
reset,
control,
} = useForm({
mode: "all",
resolver: yupResolver(schema),
defaultValues,
});
const onSubmit = (data) => {
console.log(data);
// Reset form fields
reset();
};
console.log(errors);
const Form = () => (
<>
<Typography component="h1" variant="h5">
Sign In
</Typography>
<form className={classes.form} onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="email"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="email"
label="Email Address"
{...field}
error={errors.email?.message ? true : false}
/>
)}
/>
{renderError(errors.email?.message, _MESSAGES.required)}
<Controller
control={control}
defaultValue=""
name="password"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="current-password"
type="password"
label="Password"
{...field}
error={errors.password?.message ? true : false}
/>
)}
/>
{renderError(errors.password?.message, _MESSAGES.required)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs={12}>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2" color="secondary">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</>
);
return <AuthContainer Form={<Form />} />;
};
export default SignInPage;
What might have gone wrong?
Here is a Gif of what happened:
selected the input field
started typing
after one char it leaves the input field so, I need to select it to type again.
Move your Form component to its own Component, otherwise, you are mount and unmount your Form Component each re-render.
const App = () => {
const Form = () => {} // mount and unmount with each re-render
return <div><Form /></div>
}
to
const Form = () => {}
const App = () => {
return <div><Form /></div>
}
You have to pass down the ref. For MUI TextField should do the following:
<Controller
control={methods.control}
name="fieldName"
render={({ field: { ref, ...field } }) => (
<TextField {...field} inputRef={ref} /> // ⬅️ The ref
)}
/>
A working demo: https://codesandbox.io/s/broken-microservice-xpuru
Does it work now ?
I'm building a question-set builder in reactjs, and the idea is that it will be a page on a site that will create a form with any number of multiple choice questions. Users will write the question, and then from that question, they can write four "distractors", with one of the distractors flagged as an answer. This will then go on to a parser to make it a a specific syntax, but that is beyond the scope of this question.
Right now I'm having trouble with react hooks and state in nested array. Specifically, I'm having trouble coding the part where the user creates the distractors after creating the question. Below is the code, minus some of my visual components.
import React, { useState, useForm, useEffect } from 'react';
import { Link, graphql } from 'gatsby';
// mui
import Grid from '#material-ui/core/Grid';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
// icons
import DeleteForeverIcon from '#material-ui/icons/DeleteForever';
function Question({ question, index, removeQuestion }) {
return (
<div>
<Grid container direction="row" justify="center" alignItems="center">
<Grid item xs={11} sm={10}>
<p>{question.text}</p>
</Grid>
<Grid item xs={1} sm={2}>
<p>
<Button onClick={() => removeQuestion(index)}>
<DeleteForeverIcon />
</Button>
</p>
</Grid>
</Grid>
<ul>
<Grid container container direction="row" justify="center" alignItems="center">
<Grid item xs={2}>
<Checkbox />
</Grid>
<Grid item xs={10}>
{question.distractor}
</Grid>
</Grid>
</ul>
{/* <Button variant="outlined" color="secondary">
{' '}
Add Distractor
</Button> */}
</div>
);
}
function QuestionForm({ addQuestion }) {
const [ value, setValue ] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addQuestion(value);
setValue('');
};
return (
<form onSubmit={handleSubmit}>
<TextField
fullWidth
variant="outlined"
type="text"
className="input"
placeholder=" Enter Question "
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</form>
);
}
function DistractorForm({ addDistractor }) {
const [ value, setValue ] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addDistractor(value);
setValue('');
};
return (
<form onSubmit={handleSubmit}>
<TextField
variant="outlined"
type="text"
className="input"
placeholder=" Enter Distractor "
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</form>
);
}
function QuestionBuilderPage() {
const [ title, setTitles ] = useState([ 0 ]);
const [ questions, setQuestions, distractors, setDistractors ] = useState([
{
text: 'test1',
distractors: [ 'a', 'b' ]
},
{ text: 'test2', distractors: [], isAnswer: false }
]);
const addTitle = (text) => {
const newTitles = [ { text } ];
setTitles(newTitles);
};
const addQuestion = (text) => {
const newQuestions = [ ...questions, { text } ];
setQuestions(newQuestions);
};
const addDistractor = (distractor) => {
const newDistractors = [ ...questions, { distractors } ];
setDistractors(newDistractors);
};
const removeQuestion = (index) => {
const newQuestions = [ ...questions ];
newQuestions.splice(index, 1);
setQuestions(newQuestions);
};
return (
<div className="bar-slim" />
<div className="cube-main">
<div className="cube-margin">
<form>
{/* <TitleForm addTitle={addTitle}></TitleForm> */}
<QuestionForm addQuestion={addQuestion} />
{questions.reverse().map((question, index) => (
<div>
<Question key={index} question={question} removeQuestion={removeQuestion} />
<DistractorForm addDistractor={addDistractor} />
</div>
))}
<br />
<Button type="submit" color="primary" variant="contained">
Submit
</Button>
</form>
</div>
</div>
);
}
export default QuestionBuilderPage;
Adding questions works perfectly fine, but I'm stumbling on adding the four distractors per question, and can't seem to find any resources online from a similiar project. This is my first time using hooks, and appreciate any and all advice.
I have created a form with material ui, and I would like to clear it. I have searched for answers/solutions across Stackoverflow but all of them are not working for me.
I have tried by using setstate but it not result to clear the form.
I want to know how to do this using onclick event of the button.
import React, { Component } from "react";
import {
Grid,
TextField,
AppBar,
Typography,
Toolbar,
Button
} from "#material-ui/core";
export class MyInput extends Component {
constructor(props) {
super(props);
this.state = {
first_name: "",
last_name: ""
};
}
handle_input = (name, value) => {
let cur_state = this.state;
cur_state[name] = value;
this.setState(cur_state);
};
render() {
return (
<Grid container justify="center">
<Grid item md={4}>
<TextField
defaultValue={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
</Grid>
<Grid item md={4}>
<TextField
defaultValue={this.state.last_name}
variant="outlined"
label="Last Name"
onKeyUp={(e) => {
this.handle_input("last_name", e
.target.value);
}}
/>
</Grid>
<Grid item md={4}>
<div
style={{
width: "100%",
padding: "10px",
display: "flex",
justifyContent: "space-between"
}}
>
<Button color="primary" variant="contained">
save
</Button>
<Button
color="secondary"
variant="contained"
onClick={() => {
this.setState({
first_name: "",
last_name: ""
});
}}
>
cancel
</Button>
</div>
</Grid>
</Grid>
);
}
}
export default MyInput;
Inside the TextField set a value,
<TextField
value={this.state.first_name}
defaultValue={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
Then when you want to clear it, just use setState
this.setState({ first_name: '' })
Edit:
In fact, you can delete defaultValue as the default is an empty string anyways, which is what you're using.
<TextField
value={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
It may be worth reading the react docs, basically you're wanting to override the form's internal state, which you do by using the value prop
I dont know if this will help as I am pretty new to react myself but you could try making a functional component instead of a class to help reduce the amount of code you need, i would do something like this
import React, {useState} from "react";
export default function MyInput(props) {
const [myState, setMyState] = useState(props);
function clearState() {
setMyState.first_name("")
setMyState.last_name("")
}
return (
//Your grid code here
)
//set your button on click to this
onClick = {() => {clearState()}}
Look into writing components like this its quite nice. Again this will need tweaking but may be a point in the right direction.