react router link with material ui button submit - reactjs

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>

Related

How to set values in a multi-step Formik form with components that implement useField()

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>

react onChange on text field update whole component when state is changed

I had a functional component and using react hooks to manage the state.
As soon as I add onChange in textField(input in material UI) the whole components re-render and when I remove the onChange in textField it does not re-render
<TextField
id='outlined-basic'
label='Outlined'
variant='outlined'
fullWidth
onChange={e => {
setChannelUrl(e.target.value);
}}
/>
The above picture is of component is rerender even I am not passing any props.
As soon as I remove setChannelUrl(e.target.value); everythng works like fine.
setChannelUrl is react useState Hooks
const [channelUrl, setChannelUrl] = useState('');
useEffect(() => {
console.log('runnedddd');
return () => {};
}, []);
const classes = useStyle();
return (
<Grid container spacing={2} className={classes.root}>
<Grid item md={6}>
<Typography variant='h6' color='primary' className={classes.button}>
Please Enter Your Channel Url
</Typography>
<TextField
id='outlined-basic'
label='Outlined'
variant='outlined'
fullWidth
onChange={e => {
setChannelUrl(e.target.value);
}}
/>
<Button fullWidth color='primary' variant='contained'>
large
</Button>
</Grid>
<Grid item md={6}>
<Paper elevation={2} className={`flex flexColumn ${classes.w100}`}>
<Avatar alt='Remy Sharp' src=''>
H
</Avatar>
<Typography variant='body' align='center'>
Hitesh Choudary
</Typography>
<Button
fullWidth
color='primary'
variant='contained'
className={classes.button}
>
Save It
</Button>
<Button
fullWidth
color='secondary'
variant='contained'
className={classes.button}
>
No, its not mine
</Button>
<Stats />
</Paper>
</Grid>
</Grid>
);
};
stats component
import React from 'react';
const Stats = props => {
console.log(props);
return <div></div>;
};
export default Stats;
I am using functional component and arrow function
Stats component renders duo to its parent render.
The parent renders when users typing in text field and onChange updates the stated through setChannelUrl.
You can memoize Stats with React.memo which will prevent the undesired renders:
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
const Stats = () => {
// Won't log on parent render
console.log('rendered');
return <div></div>;
};
export default React.memo(Stats);

react is not firing form onSubmit function

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

MaterialUI component is not working for react component

I am trying to use MUI readymade components in my react application,
I import everything correctly, after all these when I am running the app, there is the only element shows in the browser, not its style.
import React, { Component } from 'react';
import Button from '../node_modules/muicss/lib/react/button';
class App extends Component {
render() {
return (
<div>
<div>
<Button size="small" color="primary">button</Button>
<Button size="small" color="primary" variant="flat">button</Button>
<Button size="small" color="primary" variant="raised">button</Button>
<Button size="small" color="primary" variant="fab">+</Button>
</div>
<div>
<Button color="primary">button</Button>
<Button color="primary" variant="flat">button</Button>
<Button color="primary" variant="raised">button</Button>
<Button color="primary" variant="fab">+</Button>
</div>
<div>
<Button size="large" color="primary">button</Button>
<Button size="large" color="primary" variant="flat">button</Button>
<Button size="large" color="primary" variant="raised">button</Button>
<Button size="large" color="primary" variant="fab">+</Button>
</div>
</div>
);
}
}
export default App;
You dont need to set the exact path for node_modules just type the component name for importing any component.
Like below import statement in material ui doc.
import Button from '#material-ui/core/Button';

React Passing data to components

I have a parent stateful component and i pass the state of show dialog to a stateless header component.
When a icon clicked on the header component it opens a stateless dialog component.
In the stateless dialog component i want to be able to enter data into a text-field.
Do i have to completely change my code to make the stateless dialog to a stateful component?
Below is my code. If anyone can recommend the best way of doing this. Thanks.
class Layout extends Component {
state = {
show:false
}
toggleSidenav = (action) =>{
this.setState({
showDialog:action
})
}
render(){
return(
<div>
<Header
showNav={this.state.showDialog}
onHideNav={() => this.toggleSidenav(false)}
onOpenNav={() => this.toggleSidenav(true)}
/>
</div>
)
}
}
export default Layout;
Header component
const Header = (props) => {
console.log(props.onOpenNav)
const navBars = () => (
<div>
<AppBar position="static">
<Toolbar>
<IconButton color="inherit" aria-label="createfolder">
<SvgIcon>
<path d={createfolder}
onClick={props.onOpenNav}
name="firstName" />
</SvgIcon>
</IconButton>
</Toolbar>
</AppBar>
</div>
)
return (
<div>
<SideNav {...props} />
<div>
{navBars()}
</div>
</div>
)
}
Dialog Component
const DialogBox = (props) => {
return (
<div>
<Dialog
open={props.showNav}
aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Add Folder</DialogTitle>
<DialogContent>
<TextField
margin="normal"
/>
</DialogContent>
<DialogActions>
<Button onClick={props.onHideNav} color="primary">
Cancel
</Button>
<Button onClick={props.onHideNav} color="primary"
onChange={this.handleFieldChange}
value={this.value}
>
Create
</Button>
</DialogActions>
</Dialog>
</div>
)
}
Since component Header is readily stateful. You could initialize its state to
state = {
show:false,
formData: {} //later, you may save user input from the child component here
}
and in Header component, you may add a function:
handleInputEntered = (event) => {
const _data = { ...this.state.formData };
_data[event.target.name] = event.target.value;
this.setState({
formData: _data
});
}
and make sure to pass this new function as a prop to like this:
<Header
handleInputEntered = {this.handleInputEntered}
/>
and set onChange to be this new function where you have input field:
<TextField
onChange={this.props.handleInputEntered}
/>
It seems you're using MaterialUI, so just look up how you may supply the onChange property to TextField component.
Is this clear?

Resources