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.
Related
I am new to ReactJS with MUI development, have below ReactJS TypeScript with MuiText filed form. Looking some help to use useSate method to change the textfiled value.
Also add the onchnage function for the text filed. I can add the onchange function for normal text filed, unsure how to add it for MUI Text filed?
import * as React from 'react';
import { useState } from "react"
import Button from '#mui/material/Button';
import CssBaseline from '#mui/material/CssBaseline';
import TextField from '#mui/material/TextField';
import Grid from '#mui/material/Grid';
import Box from '#mui/material/Box';
import Container from '#mui/material/Container';
import { createTheme, ThemeProvider } from '#mui/material/styles';
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers/yup';
interface IFormInputs {
filepath: string;
}
const schema = yup.object().shape({
filepath: yup.string().min(4).required(),
});
const theme = createTheme();
export default function MuiTextField() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<IFormInputs>({
resolver: yupResolver(schema),
});
const [filepath, setFilepath] = useState("vodeo.mp3");
const onSubmit: SubmitHandler<IFormInputs> = (data) => {
console.log('data submitted: ', data);
console.log('filepath: ', data.filepath);
};
return (
<ThemeProvider theme={theme}>
<Container component="main" maxWidth="lg">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Box sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={16} sm={6}>
<Controller
name="filepath"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
{...field}
label="File Path"
error={!!errors.filepath}
helperText={errors.filepath ? errors.filepath?.message : ''}
autoComplete="file-path"
fullWidth
/>
)}
/>
</Grid>
<Button
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Submit
</Button>
</Grid>
</Box>
</form>
</Box>
</Container>
</ThemeProvider>
);
}
Update:
Here is the codeshare: https://codesandbox.io/s/boring-water-m47uxn?file=/src/App.tsx
When we change the text box value to auto, want to change the textbox value to audio.mp3. but its not working.
MUI Textfield have onChange too:
<TextField
error={Boolean(touched.name && errors.name)}
fullWidth
label={t('Name')}
name="name"
onBlur={handleBlur}
onChange={handleChange}
value={values.name}
variant="outlined"
autoComplete="off"
/>
'field' in render function contains onChange.
And state of form saved in useForm. In useForm props you have to add defaultValues. And you did not pass prop type="file", maybe its your problem.
Guide about create file input with react hook form: https://refine.dev/blog/how-to-multipart-file-upload-with-react-hook-form/
Textfield api: https://mui.com/material-ui/api/text-field/
I'm having a problem with a react component. It's supposed to test, whenever the field's value is changed, if the value on the field is different from the prop value, but once changed it seems to change the prop too.
handleChange = (id, event) => {
this.props.test(id, event.target.value)
if (this.validate(event.target.value) || (event.target.value === "" && !this.props.required)) {
this.setState({ valid: true })
this.props.canSubmit(true)
}
else {
this.setState({ valid: false })
this.props.canSubmit(false)
}
if (event.target.value !== this.props.field_value) {
this.setState({ changed: true })
}
else {
this.setState({ changed: false })
}
render() {
return (
<div className="mb-3" >
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<span>
<TextField id="outlined-basic" variant="standard" type="text" aria-describedby="emailHelp"
value={this.props.field_value || ""}
onChange={event => this.handleChange(this.props.field_id, event)}
label={this.props.field_label}
error={!this.state.valid}
/>
<Icon onClick={this.handleDescription} className="description">help_outlined</Icon>
{this.state.changed ? <Icon>edit</Icon> : null}
</span>
</div>
I've also tried saving the prop value in a state, which also doesn't work. Does anyone have any idea?
Edit:
For more clarification, the problem I'm having is that the prop value is changed whenever the field value is changed, which means that once I change the value in the field, it's always going to think it's different from the original value. For exemple: let's say an email field has a default value of "a#a.com", then I change it to "b#a.com". Now it says it's different, and it's correct. The problem
is when I change it back to "a#a.com", now it considers the prop value as the former "b#a.com" and therefore thinks its different even tho it's the same.
You have set flag which is set to true/false in your code, which itself tells you whether or not you have anything in your text field or not. The flag does not tell you what value you have in the text field.
So, it will be something like code below if its what your searching for:
import React, { Component } from "react";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
class MainPage extends Component {
constructor() {
super();
this.state = {
title: "",
subtitle: "",
flag: false,
};
}
handleFilterChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
if (this.state.title || this.state.subtitle) {
this.setState({ flag: true });
} else {
this.setState({ flag: false });
}
};
fetchOrdersByQuery = async () => {
//code for search
}
resetFilter = async () => {
this.setState({
head: "",
subhead: "",
flag: false,
});
};
render() {
const { classes } = this.props;
return (
<Grid>
<Grid item xs={4}>
<TextField
fullWidth
margin="dense"
name="title"
label="Title"
value={this.state.title}
variant="outlined"
onChange={this.handleFilterChange}
/>
</Grid>
<Grid item xs={4}>
<TextField
fullWidth
margin="dense"
name="subtitle"
label="Sub-Title"
value={this.state.subtitle}
variant="outlined"
onChange={this.handleFilterChange}
/>
</Grid>
<Grid item xs={2}>
<Button
type="submit"
fullWidth
variant="contained"
margin="normal"
onClick={this.resetFilter}
>
{" "}
Reset
</Button>
</Grid>
<Grid item xs={2}>
<Button
type="submit"
fullWidth
variant="contained"
margin="normal"
color="primary"
onClick={this.fetchOrdersByQuery}
>
Search
</Button>
</Grid>
</Grid>
);
}
}
export default (MainPage);
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
I'm trying to make a Formik wrapper which takes children as props and would render anything put inside. There are a couple forms to make which take different initial values and validation schema etc. The only thing in common thing is the grid layout. The goal is to have the access to Formik props like values, errors etc. in the child component and I have no idea how to pass it to its child. The form fields don't even show up.
The wrapper:
import React from 'react';
import { Formik, FormikConfig, FormikValues } from "formik";
import { Col, Layout, Row } from "antd";
const FormContainer: React.FC<FormikConfig<FormikValues>> = ({ children, ...props }) => {
return <Formik
{...props}
>
{props => (
<Layout>
<Row style={{ height: "100vh", display: "flex", alignItems: "center" }}>
<Col span={12}>
<Layout>
{/*this will be replaced with some background image*/}
<pre>{JSON.stringify(props.values, null, 2)}</pre>
<pre>{JSON.stringify(props.errors, null, 2)}</pre>
</Layout>
</Col>
<Col span={12}>
<Layout>
{/*here goes goes a Form from a different components*/}
{children}
</Layout>
</Col>
</Row>
</Layout>
)}
</Formik>
};
export default FormContainer;
I must be doing something wrong. I am unable to get any Formik props/values from anywhere else when I wrap FormContainer around anything.
My form example (so far):
import React from "react";
import { Field, Form } from "formik";
import { Col, Form as AntForm, Icon, Input, Row } from "antd";
import { initialValues, validationSchema } from "./fieldValidation";
import FormContainer from "../../../containers/FormContainer/FormContainer";
const RegisterPage: React.FC = () => {
return (
<FormContainer
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(data, { setSubmitting }) => {
setSubmitting(true);
setTimeout(() => {
alert(JSON.stringify(data, null, 2));
setSubmitting(false);
}, 5000);
}}
>
{({touched, errors}) => (
<Form>
<Row gutter={[8, 8]}>
<Col span={12}>
<AntForm.Item
help={touched.firstName && errors.firstName ? errors.firstName : ""}
validateStatus={touched.firstName && errors.firstName ? "error" : undefined}
>
<Field
name="firstName"
prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="First name"
as={Input}
/>
</AntForm.Item>
</Col>
<Col span={12}>
<AntForm.Item
help={touched.lastName && errors.lastName ? errors.lastName : ""}
validateStatus={touched.lastName && errors.lastName ? "error" : undefined}
>
<Field
name="lastName"
prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="Last name"
as={Input}
/>
</AntForm.Item>
</Col>
</Row>
</Form>
)}
</FormContainer>
);
};
export default RegisterPage;
I'm stuck. What am I doing wrong here?
Here's how to pass the prop "propsToPass" from the parent to all his direct children:
const Parent = props => {
const { children } = props;
const childrenWithExtraProp = React.Children.map(children, child =>
React.cloneElement(child, { propsToPass: "toChildren" })
);
return <div>{childrenWithExtraProp}</div>;
};
export default Parent;
So in this case, both children will have the prop "propsToPass"
<Parent>
{/* this.props.propsToPass will be available in this component */}
<Child></Child>
{/* this.props.propsToPass will be available in this component */}
<AnotherChild></AnotherChild>
</Parent>
You could do the same for your form.
I don't see like rendering Formik as children is good idea here, especially that you are supposed to render one form in such FormWrapper. I would use render props here, so here is basic example how you can do it.
Anyway, I still can't get your concept of re-inventing FormWrapper if Formik provides its own wrapper:
https://jaredpalmer.com/formik/docs/api/formik
interface FormWrapperProps extends FormikConfig<FormikValues> {
renderForm(props: FormWrapperProps): React.ReactNode
}
export const RegisterForm = (props: FormWrapperProps) => (
<form>
<input type="text"/>
<input type="text"/>
</form>
)
const FormWrapper: React.FC<FormWrapperProps> = (props) => {
return (
<div className="layout">
{/*here goes goes a Form from a different components*/}
{props.renderForm(props)}
</div>
)
}
const FormPage = () => {
const props = {} as FormWrapperProps
return (
<FormWrapper
{...props}
renderForm={(props: FormWrapperProps) => <RegisterForm {...props} />}
/>
)
}
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.