I'm trying to submit form onSubmitbut its not firing the this.commentSubmit function, if i take <form></form> out, use <Button onSubmit> function it works however i need the form wrapped around the Textfield for the backend to read the req.body.comment_body to work.
Comment.js
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => (
<form onSubmit={props.onSubmit}>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
<Button type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button>
</form>
)
export default Comment;
Image Container Component
import React from "react";
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import Divider from '#material-ui/core/Divider';
import Image from './Image';
import Axios from '../Axios';
import moment from 'moment';
import Comment from './Comment';
class ImageContainer extends React.Component{
state = {
isComment: false,
comment_body: ""
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
writeComment = (id) => {
this.setState({
isComment: this.state.isComment ? '' : id // check if you state is filled to toggle on/off comment
})
}
commentSubmit = (e) => {
e.preventDefault();
console.log(this.state.comment_body); // doesn't get console.log
Axios.post('/images/newComment', this.state.comment_body).then( (response )=> {
const newComment = { ...response.data};
console.log(newComment);
this.setState({
comment_body: ''
})
})
}
render(){
const { img, deleteImg } = this.props
return(
<Grid item sm={12} md={12} style={{ margin: '30px 0px'}}>
<Paper style={{padding:'20px 20px'}}>
{/* // empty image_title */}
<Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography>
<Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
<Image image_url={img.img_url} />
<Typography variant="h6" align="center">{img.user.username}</Typography>
<Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography>
<Button onClick ={() => this.writeComment(img.id)} variant="outlined" component="span" color="primary">
{this.state.isComment === img.id ? "Close" : "Write A Comment"}
</Button>
{/* here were prevent comments being selected for all items in the array, renders the comment form you clicked on. */}
{this.state.isComment === img.id ?
<Comment onSubmit={this.commentSubmit}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
: null}
{/* hide delete button when user enters comment */}
{!this.state.isComment ? <Button style={{margin: '0px 20px'}} onClick={deleteImg} variant="outlined" component="span" color="primary">
Delete
</Button> : null}
</Paper>
</Grid>
)
}
}
export default ImageContainer
Alternatively this works but i don't think the back end reads the comment_body value
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => {
// <form onSubmit={props.onSubmit}>
return(
<div>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
<Button onClick={props.onSubmit} type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button>
</div>
);
// </form>
}
export default Comment;
backend
router.post('/newComment', async (req, res) => {
const comment = new Comment({
comment_body: req.body.comment_body,
user_id: req.user.id
})
comment.save().then( (comment) => {
return res.status(200).json(comment);
})
})
The problem lies with <Button> from Material-ui not being an actual button but rather a combination of a <span>. So if you have a type="submit" the form doesn't do anything.
If you change your material-ui <Button> to a native <button> it works as expected.
Here's an example: https://codesandbox.io/embed/56615445-fj6sc
Related
I'm implementing the multi-step wizard example with Material-UI components and it works well with the useField() hook but I cannot figure out how to bring setFieldValue() into scope, so I can use it from a wizard step.
I've seen suggestions to use the connect() higher-order component but I have no idea how to do that.
Here is a snippet of my code: CodeSandbox, and the use case:
A wizard step has some optional fields that can be shown/hidden using a Material-UI Switch. I would like the values in the optional fields to be cleared when the switch is toggled off.
I.e.
Toggle switch on.
Enter data in Comments field.
Toggle switch off.
Comments value is cleared.
Toggle switch on.
Comments field is empty.
Hoping someone can help! Thanks.
I came across this answer the other day but discarded it because I couldn't get it working.
It does actually work but I'm in two minds as to whether it's the right approach.
const handleOptionalChange = (form) => {
setOptional(!optional)
form.setFieldValue('optionalComments', '', false)
}
<FormGroup>
<FormControlLabel
control={
// As this element is not a Formik field, it has no access to the Formik context.
// Wrap with Field to gain access to the context.
<Field>
{({ field, form }) => (
<Switch
checked={optional}
onChange={() => handleOptionalChange(form)}
name="optional"
color="primary"
/>
)}
</Field>
}
label="Optional"
/>
</FormGroup>
CodeSandbox.
I believe this is what you're after: CodeSandbox. I forked your CodeSandbox.
I tried to follow your code as closely as possible and ended up not using WizardStep. The step variable is returning a React component that is a child to Formik. Formik is rendered with props e.g. setFieldValue, which can be passed down to its children. In order to pass the setFieldValue as a prop to step, I had to use cloneElement() (https://reactjs.org/docs/react-api.html#cloneelement), which allows me to clone the step component and add props to it as follows.
// FormikWizard.js
<Formik
initialValues={snapshot}
onSubmit={handleSubmit}
validate={step.props.validate}
>
{(formik) => (
<Form>
<DialogContent className={classes.wizardDialogContent}>
<Stepper
className={classes.wizardDialogStepper}
activeStep={stepNumber}
alternativeLabel
>
{steps.map((step) => (
<Step key={step.props.name}>
<StepLabel>{step.props.name}</StepLabel>
</Step>
))}
</Stepper>
<Box
className={classes.wizardStepContent}
data-cy="wizardStepContent"
>
{React.cloneElement(step, {
setFieldValue: formik.setFieldValue
})}
</Box>
</DialogContent>
<DialogActions
className={classes.wizardDialogActions}
data-cy="wizardDialogActions"
>
<Button onClick={handleCancel} color="primary">
Cancel
</Button>
<Button
disabled={stepNumber <= 0}
onClick={() => handleBack(formik.values)}
color="primary"
>
Back
</Button>
<Button
disabled={formik.isSubmitting}
type="submit"
variant="contained"
color="primary"
>
{isFinalStep ? "Submit" : "Next"}
</Button>
</DialogActions>
</Form>
)}
</Formik>
To access the setFieldValue prop in the child component, in App.js, I created a new component called StepOne and used it to wrap around the inputs, instead of using WizardStep. Now I am able to access setFieldValue and use it in the handleOptionalChange function.
// App.js
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Box from "#material-ui/core/Box";
import CssBaseline from "#material-ui/core/CssBaseline";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import FormGroup from "#material-ui/core/FormGroup";
import Switch from "#material-ui/core/Switch";
import FormikTextField from "./FormikTextField";
import { Wizard, WizardStep } from "./FormikWizard";
const useStyles = makeStyles((theme) => ({
content: {
display: "flex",
flexFlow: "column nowrap",
alignItems: "center",
width: "100%"
}
}));
const initialValues = {
forename: "",
surname: "",
optionalComments: ""
};
const StepOne = ({ setFieldValue }) => {
const classes = useStyles();
const [optional, setOptional] = useState(false);
const displayOptional = optional ? null : "none";
const handleOptionalChange = () => {
setFieldValue("optionalComments", "");
setOptional(!optional);
};
return (
<Box className={classes.content}>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="forename"
label="Forename"
type="text"
/>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="surname"
label="Surname"
type="text"
/>
<FormGroup>
<FormControlLabel
control={
<Switch
checked={optional}
onChange={handleOptionalChange}
name="optional"
color="primary"
/>
}
label="Optional"
/>
</FormGroup>
<FormikTextField
style={{ display: displayOptional }}
fullWidth
size="small"
variant="outlined"
name="optionalComments"
label="Comments"
type="text"
/>
</Box>
);
};
function App(props) {
return (
<>
<CssBaseline />
<Wizard
title="My Wizard"
open={true}
initialValues={initialValues}
onCancel={() => {
return;
}}
onSubmit={async (values) => {
console.log(JSON.stringify(values));
}}
>
<StepOne />
<StepTwo />
</Wizard>
</>
);
}
export default App;
Alternative
To use setFieldValue in Formik, the easiest way would be to have the all input elements within the <Formik></Formik tags. You could conditionally render the input elements based on what step you're on as follows. This gives the inputs a direct access to setFieldValue so you can call setFieldValue("optionalComments", "") on the Switch input which will clear the comments on each toggle. Although this may mean you'll have a longer form, I don't think this is necessarily a bad thing.
<Formik>
<Form>
{step === 1 && <div>
// Insert inputs here
</div>}
{step === 2 && <div>
<TextField
onChange={(event) => setFieldValue("someField", event.target.value)}
/>
<Switch
checked={optional}
onChange={() => {
setFieldValue("optionalComments", "");
setOptional(!optional);
}}
name="optional"
color="primary"
/>
</div>}
</Form>
</Formik>
I am building a login form with Formik with Materail UI, but Formik doesn't recognize Button of Material UI. If I replace the Button with html button everything works. Could anybody explain why it's not working with the Button component. Below is my code:
import React, { ReactElement } from "react";
import TextField from "#material-ui/core/TextField";
import FormControl from "#material-ui/core/FormControl";
import { Formik, Form } from "formik";
import { makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const useStyles = makeStyles((theme) => ({
root: {
margin: theme.spacing(1),
width: 200,
display: "flex",
},
input: {
marginTop: 5,
marginBottom: 5,
},
}));
interface Props {}
export default function loginForm({}: Props): ReactElement {
const classes = useStyles();
return (
<Box className={classes.root}>
<Formik
initialValues={{ username: "", password: "" }}
validate={(values) => {
const errors: { username?: string; password?: string } = {};
if (!values.username) {
errors.username = "Required";
} else if (!values.password) {
errors.password = "Required";
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
console.log("values: ", values);
setSubmitting(false);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<FormControl>
<TextField
className={classes.input}
error={errors.username ? true : false}
type="username"
name="username"
onChange={handleChange}
onBlur={handleBlur}
value={values.username}
label="Username"
variant="outlined"
helperText={
errors.username && touched.username && errors.username
}
/>
<TextField
className={classes.input}
error={errors.password ? true : false}
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
label="Password"
variant="outlined"
helperText={
errors.password && touched.password && errors.password
}
/>
</FormControl>
<Box display="flex" justifyContent="space-between">
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
>
Submit
</Button>
<Button
variant="contained"
color="secondary"
disabled={isSubmitting}
>
Cancel
</Button>
</Box>
</form>
)}
</Formik>
</Box>
);
}
please note the code above doesn't work, but if you replace Button with button, the form works.
In most browsers, a HTML button by default has the type=submit which means that Formik's submit handler will be called. A Material-UI button does not have this default so the submit handler will never be called. Try adding type=submit to your <Button> props.
(Also, check out Formik's Material-UI integration examples)
You should add the id to the form.
<form onSubmit={handleSubmit} id="myForm">
And bind the form id with submit button
<Button
type="submit"
form="myForm"
>
I'm trying to figure out how to submit form data to firestore from a formik form in a react app.
I've used this tutorial to try to learn how to make the form and then tried to add the firebase form submission to the submit handler.
The form has:
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import firebase from '../../../../firebase';
// import firestore from '../../../../firebase';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
// import axios from 'axios';
import {
Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
// import { DisplayFormikState } from './formikHelper';
const styles = {
};
function Contact(props) {
const { classes } = props;
const [open, setOpen] = useState(false);
const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
function handleClose() {
setOpen(false);
}
function handleClickOpen() {
setSubmitionCompleted(false);
setOpen(true);
}
return (
<React.Fragment>
<Link
// component="button"
className="footerlinks"
onClick={handleClickOpen}
>
Contact
</Link>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Contact Us</DialogTitle>
<DialogContent>
<DialogContentText>
Thanks for your interest.
</DialogContentText>
<Formik
initialValues={{ email: '', name: '', comment: '' }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firebase.firestore.collection("contact").doc.set({
name: "name",
email: "email",
comment: "comment"
})
// axios.post(contactFormEndpoint,
// values,
// {
// headers: {
// 'Access-Control-Allow-Origin': '*',
// 'Content-Type': 'application/json',
// }
// },
.then(() => {
setSubmitionCompleted(true);
});
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required('Required'),
name: Yup.string()
.required('Required'),
comment: Yup.string()
.required('Required'),
})}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<form onSubmit={handleSubmit}>
<TextField
label="Name"
name="name"
className={classes.textField}
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.name && touched.name) && errors.name}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
error={errors.email && touched.email}
label="Email"
name="email"
className={classes.textField}
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.email && touched.email) && errors.email}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
label="Let us know how we can help"
name="comment"
className={classes.textField}
multiline
rows={4}
value={values.comment}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.comment && touched.comment) && errors.comment}
margin="normal"
style={{ width: "100%"}}
/>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</Button>
<Button type="submit" disabled={isSubmitting}>
Submit
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</form>
);
}}
</Formik>
</DialogContent>
</React.Fragment>
}
{isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
<DialogContent>
<DialogContentText>
Thanks
</DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Back to app
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default withStyles(styles)(Contact);
The firestore config file has:
When I try this, I get a warning in the console when I press submit (no error messages and the form just hangs with data that I entered in the form.
instrument.ts:129 Warning: An unhandled error was caught from
submitForm() TypeError:
firebase__WEBPACK_IMPORTED_MODULE_2_.default.firestore.collection is not a function
Note, I've dried both .doc.set and .doc.add in the firestore method - neither works.
Found it eventually -the doc has to be doc()
Here is my code.
My goal is: when I click on Login button, content will show the Login card and when I click on Register button content will show the Register card.
As for my primary test, I used the following code:
import React,{useState} from 'react'
const Login=()=>{
return(
<form>
<label>Username:</label>
<input type="text" value="username"/>
<label>Password:</label>
<input type="password" value="password"/>
<button>Submit</button>
</form>
)
}
const Register=()=>{
return(
<form>
<label>Name:</label>
<input type="text" value="name"/>
<label>Username:</label>
<input type="text" value="username"/>
<label>Password:</label>
<input type="password" value="password"/>
<button>Register</button>
</form>
)
}
export default function App() {
const [click,setClick]=useState(false)
const handleClick=()=>{
setClick(true)
}
return (
<div>
<button onClick={handleClick}>Login</button>
{click?<Login/>:<Register/>}
</div>
)
}
And it works. In this way I tried to render functionalities in different components. It seems like not working. What am I missing here?
The following corrections will help you:-
You need to make App component responsible for maintaining click state and then pass only handler (setClick) to AppBar and only state (click) to Content.
import React, { useState } from "react";
import Login from "./Login";
import Register from "./Register";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import { Grid } from "#material-ui/core";
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1
},
menuButton: {
marginRight: theme.spacing(2)
},
title: {
flexGrow: 1
}
}));
function Appbar({ handleClick }) {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
>
<MenuIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
ReactApp
</Typography>
<Button color="inherit" onClick={()=>handleClick(true)}>
Login
</Button>
<Button color="inherit" onClick={()=>handleClick(false)}>
Register
</Button>
</Toolbar>
</AppBar>
</div>
);
}
function Content({ click }) {
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container style={{ padding: 80 }} item>
<Grid xs={false} sm={4} />
<Grid xs={12} sm={8} />
{click ? (
<Login onClick={handleClick} />
) : (
<Register onClick={handleClick} />
)}
<Grid />
<Grid xs={false} sm={2} />
</Grid>
</div>
);
}
export default function App() {
const [click, setClick] = useState(true);
const handleClick = (value) => {
setClick(value);
};
return (
<div>
<Appbar handleClick={handleClick} />
<Content click={click} />
</div>
);
}
I am using flask on the backend and react, react router and material-ui as a frontend.
I have:
material-ui/core 3.4.0,
react router dom 4.3.1
I am trying to create simple sign up form.
I would like to submit the form's data and then redirect to the index page using react router.
My problem is that material-ui button does not work with the link from router.
Is it possible to combine this two together somehow?
My code:
import:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { withStyles, Grid, Paper, Avatar, Typography, Button, TextField} from '#material-ui/core'
import LockIcon from '#material-ui/icons/LockOutlined'
and render return:
render() {
const { classes } = this.props
const { user: { username, password } } = this.state
return (
<Grid container className={classes.root}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign up
</Typography>
<form onSubmit={this.saveUser}>
<TextField label="Username" value={username} onChange={this.handleChange('username')} margin="normal" required fullWidth/>
<TextField label="Password" value={password} onChange={this.handleChange('password')} margin="normal" required fullWidth type='password'/>
<Button component={Link} to="/" type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
</form>
</Paper>
</Grid>
);
}
When I remove from Button component={Link} to="/":
<Button type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
then submit work and saves data in database. When I remove type="submit":
<Button component={Link} to="/" fullWidth variant="contained" color="primary">Sign Up</Button>
then redirect to index page.
Can I combine somehow this two in one button?
RaisedButton, FlatButton from material-ui not working anymore
Add to state:
doRedirect: false
In render():
<Button type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
In submit hanler:
saveUser = () => {
// **********
// Your code to update database
// **********
this.setState({ doRedirect: true });
}
In render():
{ this.state.doRedirect && <Redirect to="/" /> }
And of course:
import { Redirect } from 'react-router-dom'
In your save user function just redirect after saving the user.
you can get history from props
saveUser = event => {
event.preventDefault()
//update database with user
this.props.history.location.push('new-route')
// or you can use window.location
window.location.href = 'new-route'
}
<Button
onClick={() => {}}
variant="outlined"
component={Link}
to={{
pathname: `/`,
search: location.search,
}}
>
Submit
</Button>