Required TextField validation on Material-UI - reactjs

If something is written on the required TextField then everything will be okay but if the required TextField is blank then it will show me an error message on a helperText. But the problem is I can't use lenth on a if condition where I have set an onClick handler or method but I can use lenth on an onChange handler or method. If I can't use lenth then how will I know that something is written on required TextField. Here is my code:
import React, { Component } from 'react';
import { Container, Grid, Typography, TextField, Button } from '#material-ui/core';
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
mT: {
marginTop: theme.spacing(1)
},
mR: {
marginRight: theme.spacing(3)
}
});
class Account extends Component {
constructor(){
super()
this.state={
accname: '',
helperText: '',
error: false
}
this.onChange = this.onChange.bind(this);
this.onClick = this.onClick.bind(this);
}
onChange = (event) => {
this.setState({[event.target.name]:event.target.value})
}
onClick = (event) => {
if (event.target.required > 0) {
this.setState({ helperText: '', error: false });
} else {
this.setState({ helperText: 'Invalid format', error: true });
}
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<Container>
<Grid container
spacing={2}>
<Grid item
xs={12}
sm={12}
md={12}
lg={12}
xl={12}>
<Typography className={classes.mT}
variant="h4"
color="textPrimary">
Add Account
</Typography>
</Grid>
<Grid item
xs={12}
sm={12}
md={12}
lg={12}
xl={12}>
<TextField className={classes.mR}
label="Account Name"
name="accname"
type="text"
helperText={this.state.helperText}
onChange={this.onChange}
value={this.state.accname}
error={this.state.error}
variant="outlined"
color="primary"
size="small"
autoFocus
required />
<Button variant="contained"
color="primary"
onClick={this.onClick} >
Submit
</Button>
</Grid>
</Grid>
</Container>
</React.Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Account);

onClick = () => {
if(
this.state.accname.trim() != '' &&
this.state.firstName.trim() != '' &&
this.state.lastName.trim() != ''
){
//Your API call here
}
else{
alert("fill the empty spaces!");
}

Related

Material-UI FormControl InputLabel display problem

In the picture, you can see that my label is behind the outline but I don't know why it is. I didn't give any kind of style I just gave a minWidth that's it but still, it's one kind of broken I mean the label is behind the line. Here is the code:
import React, { Component } from "react";
import {
Container, Grid, FormControl,
InputLabel, Select, MenuItem
} from "#material-ui/core";
import { withStyles } from "#material-ui/core/styles";
const styles = (theme) => ({
formControl: {
minWidth: 180
}
});
class Account extends Component {
constructor() {
super();
this.state = {
age: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => this.setState({ age: event.target.value });
render() {
const { classes } = this.props;
const { age } = this.state;
return (
<React.Fragment>
<Container>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12} xl={12}>
<FormControl
className={classes.formControl}
variant="outlined"
size="small">
<InputLabel>Age</InputLabel>
<Select value={age} onChange={this.handleChange}>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
</Container>
</React.Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Account);
The "Select" did not have enough space. To proof the theory I have added a < br > break to create some extra space. Now it displays the age above the Select Field. Please adjust the Margin Top.
import React, { Component, createRef } from "react";
import { Container, Grid, FormControl,
InputLabel, Select, MenuItem } from "#material-ui/core";
import { withStyles } from "#material-ui/core/styles";
const styles = (theme) => ({
formControl: {
minWidth: 180
}
});
class Account extends Component {
constructor() {
super();
this.state = {
age: '',
labelWidth: 0
};
this.handleChange = this.handleChange.bind(this);
this.inputLabel = createRef();
}
handleChange = (event) => this.setState({ age: event.target.value });
componentDidMount() {
this.setState({ labelWidth: this.inputLabel.current.offsetWidth });
}
render() {
const { classes } = this.props;
const { age, labelWidth } = this.state;
return (
<React.Fragment>
<Container>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12} xl={12}>
<FormControl
className={classes.formControl}
variant="outlined"
size="small"
>
<InputLabel ref={this.inputLabel}>Age</InputLabel>
<Select
value={age}
onChange={this.handleChange}
labelWidth={labelWidth}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
</Container>
</React.Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Account);

Using a checkbox with Material UI to change the boolean value of an attribute within redux

I have a form that is controlled by redux state and I need to use checkboxes that will change the value of certain attributes of state from true to false and back again. The way that I have it written, I can click the checkbox and it updates the attribute to true but when I try to click on it again, it doesn't uncheck and it doesn't change state.
Here is my form,
import React, { useEffect } from 'react';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import {Link} from 'react-router-dom'
import Grid from '#material-ui/core/Grid';
import Box from '#material-ui/core/Box';
import EditIcon from '#material-ui/icons/Edit';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import {connect} from 'react-redux'
import {updateProfileForm, setProfileForm} from '../actions/updateProfileActions'
import { update } from '../actions/currentUserActions'
import FormControl from '#material-ui/core/FormControl';
import Checkbox from '#material-ui/core/Checkbox';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormLabel from '#material-ui/core/FormLabel';
import { TimePicker } from "#material-ui/pickers"
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
NTXASN
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const UpdateProfileForm =({updateFormData, updateProfileForm, update, history, currentUser, setProfileForm })=> {
const useUpdateProfileForm = () => {
useEffect(()=> {
setProfileForm(currentUser.attributes)
}, [])
}
useUpdateProfileForm()
const classes = useStyles();
const handleChange = event => {
const {name, value } = event.target
const updatedFormInfo = {
...updateFormData,
[name]: value
}
updateProfileForm(updatedFormInfo)
}
const handleBoolean = event => {
const {name, value } = event.target
console.log(event.target.checked)
const updatedFormInfo = {
...updateFormData,
[name]: !value
}
updateProfileForm(updatedFormInfo)
console.log(event.target.checked)
}
const handleSubmit = event =>{
event.preventDefault()
update(updateFormData, history)
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<EditIcon />
</Avatar>
<Typography component="h1" variant="h5">
Update your Driver Profile
</Typography>
<form className={classes.form} noValidate onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
name="name"
variant="outlined"
required
fullWidth
id="name"
label="Name"
autoFocus
onChange={handleChange}
value={updateFormData.name}
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
onChange={handleChange}
value={updateFormData.email}
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="phone_number"
label="Phone Number"
name="phone_number"
autoComplete="phone number"
onChange={handleChange}
type="tel"
value={updateFormData.phone_number}
/>
</Grid>
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">Select Available Days</FormLabel>
<FormGroup>
<FormControlLabel
control={<Checkbox checked={updateFormData.monday} onChange={handleBoolean} name="monday" />}
label="Monday"
/>
<FormControlLabel
control={<Checkbox checked={updateFormData.tuesday} onChange={handleBoolean} name="tuesday" />}
label="Tuesday"
/>
<FormControlLabel
control={<Checkbox checked={updateFormData.wednesday} onChange={handleBoolean} name="wednesday" />}
label="Wednesday"
/>
</FormGroup>
</FormControl>
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">-</FormLabel>
<FormGroup>
<FormControlLabel
control={<Checkbox checked={updateFormData.thursday} onChange={handleBoolean} name="thursday" />}
label="Thursday"
/>
<FormControlLabel
control={<Checkbox checked={updateFormData.friday} onChange={handleBoolean} name="friday" />}
label="Friday"
/>
<FormControlLabel
control={<Checkbox checked={updateFormData.saturday} onChange={handleBoolean} name="saturday" />}
label="Saturday"
/>
</FormGroup>
</FormControl>
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">-</FormLabel>
<FormGroup>
<FormControlLabel
control={<Checkbox checked={updateFormData.sunday} onChange={handleBoolean} name="sunday" />}
label="Sunday"
/>
</FormGroup>
</FormControl>
</Grid>
<br/>
<Grid>
<TimePicker autoOk label="Hour Availability Lower Limit" value={updateFormData.availability_hours_lower} name="availability_hours_lower" onChange={handleChange}/>
<TimePicker autoOk label="Hour Availability Upper Limit" value={updateFormData.availability_hours_lower} name="availability_hours_upper" onChange={handleChange}/>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Update Profile
</Button>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
const mapStateToProps = state => {
return{
currentUser: state.currentUser,
updateFormData: state.updateProfile
}
}
export default connect(mapStateToProps, {updateProfileForm, update, setProfileForm})(UpdateProfileForm)
here are my actions
export const updateProfileForm = (formData) => {
return {
type: "UPDATE_PROFILE_FORM",
formData: formData
}
}
export const resetProfileForm = () => {
return {
type: "RESET_PROFILE_FORM",
}
}
export const setProfileForm = (formData) => {
return {
type: 'SET_PROFILE_FORM',
formData: formData
}
}
here is my reducer
const initialState = {
name: '',
email: '',
phone_number: '',
monday: false,
tuesday: false,
wednesday: false,
thursday: false,
friday: false,
saturday: false,
sunday: false,
availability_hours_lower: '',
availability_hours_upper: ''
}
const updateProfileReducer = (state = initialState, action) => {
switch(action.type){
case "UPDATE_PROFILE_FORM":
console.log(action.formData)
return action.formData
case "RESET_SIGNUP_FORM":
return initialState
case "SET_PROFILE_FORM":
return action.formData
default:
return state
}
}
export default updateProfileReducer
As I stated, right now when I click the check box it updates redux state from false to true and renders a checked box but when I try to uncheck the box, redux state is not updated and it does not uncheck the box. Any help would be greatly appreciated!
You should use the checked attribute to check if the checkbox is on or not.
const handleBoolean = (event) => {
const { name, checked } = event.target
updateProfileForm({
...updateFormData,
[name]: checked,
})
}
This is unrelated to the question but you can refactor your handleChange and handleBoolean functions by updating the reducer to apply the changes to the existing state.
const updateProfileReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_PROFILE_FORM':
return { ...state, ...action.formData }
// ...
default:
return state
}
}
const handleChange = (event) => {
const { name, value } = event.target
updateProfileForm({
[name]: value,
})
}
const handleBoolean = (event) => {
const { name, checked } = event.target
updateProfileForm({
[name]: checked,
})
}
Btw, you don't even need to manage the form state in Redux since it's a local state. I would recommend using React Hook Form to simplify the code.

Formik onSubmit error: Can't perform a React state update on an unmounted component

I get this error when trying to submit a Formik form.
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
in Formik (at layout.tsx:113)
in div (created by ForwardRef(CardContent))
in ForwardRef(CardContent) (created by WithStyles(ForwardRef(CardContent)))
in WithStyles(ForwardRef(CardContent)) (created by Context.Consumer)
in StyledComponent (created by Styled(WithStyles(ForwardRef(CardContent))))
in Styled(WithStyles(ForwardRef(CardContent))) (created by CardContent)
in CardContent (created by Context.Consumer)
in StyledComponent (created by Styled(CardContent))
in Styled(CardContent) (at layout.tsx:112)
in div (created by ForwardRef(Paper))
in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
in WithStyles(ForwardRef(Paper)) (created by ForwardRef(Card))
in ForwardRef(Card) (created by WithStyles(ForwardRef(Card)))
in WithStyles(ForwardRef(Card)) (created by Context.Consumer)
in StyledComponent (created by Styled(WithStyles(ForwardRef(Card))))
in Styled(WithStyles(ForwardRef(Card))) (created by Card)
in Card (at layout.tsx:111)
The function onSubmit has no async calls:
<Formik
...
onSubmit={(values, { setSubmitting }): void => {
setTimeout(() => {
// convert daysOfWeek to Weekday[]
const daysOfWeek = values.daysOfWeek
.filter(day => day.checked)
.map(day => day.dayOfWeek)
const edited: EditTask = { ...task, ...values, daysOfWeek }
onEditTask(edited)
setSubmitting(false)
})
}}
>
The onEditTask callback just updates the list of TaskItems.
The full TaskItem component looks like this. The '#web/core...' components are my own wrappers around Material UI components.
TaskItem.tsx
import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import Grid from '#material-ui/core/Grid'
import ListItemIcon from '#material-ui/core/ListItemIcon'
import ListItemText from '#material-ui/core/ListItemText'
import CloseIcon from '#material-ui/icons/Close'
import Typography from '#web/core/components/Typography'
import MenuButton from '#web/core/components/MenuButton'
import MenuItem from '#web/core/components/MenuItem'
import TextField from '#web/core/components/TextField'
import Checkbox from '#web/core/components/Checkbox'
import Button from '#web/core/components/Button'
import Card from '#web/core/components/Card'
import EditIcon from '#web/core/theme/icons/Edit'
import { TaskSchedule, Weekday } from 'stores/goals/models'
import {
EditTask,
getDistinctTaskSchedulesSorted,
} from 'components/CitizenDetails/DietRules/FormDrawer'
import { TaskItemViewMode } from 'components/CitizenDetails/DietRules/TaskItem'
import { Formik, FieldArray, Form } from 'formik'
import uuid from 'uuid/v4'
import * as Yup from 'yup'
import TaskOverview from '../TaskOverview'
import { DeleteIcon, DeleteMenuItemText, EditCardContent, RemoveScheduleButton } from './styled'
const taskValidationSchema = Yup.object().shape({
subject: Yup.string().required('Required'),
description: Yup.string().nullable(),
daysOfWeek: Yup.array()
.min(1, 'Min 1 day')
.required('Required'),
taskSchedules: Yup.array()
.ensure()
.min(0, 'Min 0 elements'),
})
interface Props {
viewMode: TaskItemViewMode
task: EditTask
onChangeViewMode: (viewMode: TaskItemViewMode) => void
onDeleteTask: () => void
onEditTask: (task: EditTask) => void
disableMenu?: boolean
}
export interface CheckedWeekday {
checked: boolean
dayOfWeek: Weekday
}
function mapToCheckedWeekday(weekday: Weekday, checked: boolean): CheckedWeekday {
return {
dayOfWeek: weekday,
checked,
}
}
export default function TaskItem({
viewMode,
task,
onChangeViewMode,
onDeleteTask,
onEditTask,
disableMenu,
}: Props): ReactElement {
const [t] = useTranslation()
type TaskFormValues = Pick<EditTask, 'subject' | 'description'> & {
daysOfWeek: CheckedWeekday[]
taskSchedules: TaskSchedule[]
}
const initialValues: TaskFormValues = {
...task,
daysOfWeek: Object.values(Weekday).map(day =>
mapToCheckedWeekday(day, task.daysOfWeek.includes(day))
),
taskSchedules: getDistinctTaskSchedulesSorted(task.taskSchedules),
}
return (
<>
{viewMode === TaskItemViewMode.View ? (
<Grid container justify="space-between" alignItems="center">
<Grid item>
<TaskOverview task={task} />
</Grid>
<Grid item>
{!disableMenu && (
<MenuButton id="diet-task-overview-menu-button">
<MenuItem onClick={(): void => onChangeViewMode(TaskItemViewMode.Edit)}>
<ListItemIcon>
<EditIcon />
</ListItemIcon>
<ListItemText primary={t('edit')} />
</MenuItem>
<MenuItem key="diet-task-overview-menu-remove-button" onClick={onDeleteTask}>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<DeleteMenuItemText primary={t('remove')} />
</MenuItem>
</MenuButton>
)}
</Grid>
</Grid>
) : (
<Card>
<EditCardContent>
<Formik
initialValues={initialValues}
validationSchema={taskValidationSchema}
onSubmit={(values, { setSubmitting }): void => {
setTimeout(() => {
// convert daysOfWeek to Weekday[]
const daysOfWeek = values.daysOfWeek
.filter(day => day.checked)
.map(day => day.dayOfWeek)
const edited: EditTask = { ...task, ...values, daysOfWeek }
onEditTask(edited)
setSubmitting(false)
})
}}
>
{({
values,
errors,
isValid,
touched,
dirty,
handleChange,
handleBlur,
handleSubmit,
handleReset,
isSubmitting,
validateForm,
}): ReactElement => (
<Form>
<Grid container direction="column" spacing={3}>
<Grid item>
<Typography variant="h6">{t('goals:diet.task.title')}</Typography>
</Grid>
<Grid item>
<TextField
id={`task-${task.uuid}-subject`}
label={t('title')}
name="subject"
onChange={handleChange}
onBlur={handleBlur}
value={values.subject}
fullWidth
inputProps={{ autoFocus: viewMode === TaskItemViewMode.Create }}
/>
{errors.subject && touched.subject && errors.subject}
</Grid>
<Grid item>
<TextField
id={`task-${task.uuid}-description`}
label={t('description')}
name="description"
onChange={handleChange}
onBlur={handleBlur}
value={values.description}
rows="3"
multiline
fullWidth
/>
</Grid>
<Grid item>
<Typography variant="subtitle1" gutterBottom>
{t('day').toUpperCase()}
</Typography>
<FieldArray
name="daysOfWeek"
render={({ replace }) => (
<Grid container spacing={2}>
{values.daysOfWeek.map((day, idx) => (
<Grid item key={`task-weekday-${day.dayOfWeek}`}>
<Checkbox
id={`task-weekday-checkbox-${day.dayOfWeek}`}
name={`daysOfWeek.${idx}.dayOfWeek`}
value={day.dayOfWeek}
checked={day.checked}
label={t(`weekdays.${day.dayOfWeek.toLowerCase()}`, {
context: 'short',
})}
onChange={e => {
replace(idx, { ...day, checked: !day.checked })
}}
/>
</Grid>
))}
</Grid>
)}
/>
</Grid>
<FieldArray
name="taskSchedules"
render={({ remove, replace, push }) => (
<>
<Grid item>
<Typography variant="subtitle1" gutterBottom>
{t('schedule_plural').toUpperCase()}
</Typography>
{values.taskSchedules.map((schedule, idx) => (
<Grid
container
spacing={6}
key={`task-weekday-schedule-${schedule.uuid}`}
>
<Grid item>
<TextField
type="time"
key={`task-schedule-${schedule.uuid}`}
id={`task-schedule-${schedule.uuid}`}
label={t('time')}
name={`taskSchedules.${idx}.time`}
value={schedule.time}
onChange={e => {
const time = e.target.value
replace(idx, { ...schedule, time })
}}
inputProps={{
step: 300, // 5 min
}}
/>
</Grid>
<Grid item>
<RemoveScheduleButton
id={`task-weekday-button-remove-schedule-${schedule.uuid}`}
vanilla
icon
onClick={(): void => {
remove(idx)
// bug in formik makes this necessary
// see https://github.com/jaredpalmer/formik/issues/784#issuecomment-503135849
setTimeout(() => {
validateForm()
}, 10)
}}
>
<CloseIcon />
</RemoveScheduleButton>
</Grid>
</Grid>
))}
</Grid>
<Grid item>
<Button
id="task-weekday-button-add-schedule"
ghost
onClick={(): void => {
push({
time: '00:00:00',
taskUuid: task.uuid,
uuid: uuid(),
dayOfWeek: Weekday.Monday, // temporary value
})
}}
>
{t('goals:diet.task.add-schedule')}
</Button>
</Grid>
</>
)}
/>
<Grid item>
<Grid container justify="flex-end" spacing={2}>
<Grid item>
<Button
id="task-button-cancel-editing"
size="small"
ghost
licorice
onClick={
viewMode === TaskItemViewMode.Create
? onDeleteTask
: (): void => {
handleReset()
onChangeViewMode(TaskItemViewMode.View)
}
}
>
{t('cancel')}
</Button>
</Grid>
<Grid item>
<Button
type="submit"
id="task-button-save-editing"
size="small"
disabled={!dirty || !isValid || isSubmitting}
>
{t('ok')}
</Button>
</Grid>
</Grid>
</Grid>
</Grid>
</Form>
)}
</Formik>
</EditCardContent>
</Card>
)}
</>
)
}
Probably onEditTask is changing the viewMode, removing the <EditCardContent>, so when the call to setSubmitting happens Formik is already unmounted.

react- setState doesn't clear textbox

I want to clear textbox fields on success or error. how can I do that.?
On success or error, calling a function to show the snack bar. Within that, I'm updating the state. but it is not working fine.
any help appreciated.!
function
openSnackbar = ({ message }) => {
this.setState({
open: true,
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: '',
message
});
};
textfield
<TextField
id="cp_currentPassword"
label="Current Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_currentPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>;
component
import React, { Component } from 'react'
import withStyles from "#material-ui/core/styles/withStyles";
import CircularProgress from '#material-ui/core/CircularProgress';
import TextField from '#material-ui/core/TextField';
import GridItem from "../uiComponents/Grid/GridItem.jsx";
import GridContainer from "../uiComponents/Grid/GridContainer.jsx";
import Button from "../uiComponents/CustomButtons/Button.jsx";
import Card from "../uiComponents/Card/Card.jsx";
import CardHeader from "../uiComponents/Card/CardHeader.jsx";
import CardBody from "../uiComponents/Card/CardBody.jsx";
import CardFooter from "../uiComponents/Card/CardFooter.jsx";
import Snackbar from '#material-ui/core/Snackbar';
import CloseIcon from '#material-ui/icons/Close';
import { Redirect } from 'react-router-dom'
import IconButton from '#material-ui/core/IconButton';
import { connect } from 'react-redux'
import { compose } from 'redux'
import { changePassword } from '../../store/actions/auth'
const styles = {
textField: {
fontSize: '5px'
},
};
class ChangePassword extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
open: false,
message: '',
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: ''
}
}
componentDidUpdate = (prevProps) => {
const { authError } = this.props;
console.log(authError)
if (authError != prevProps.authError) {
this.setState(
{
loading: false,
message: authError,
open: true
})
}
};
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
openSnackbar = ({ message }) => {
this.setState({
open: true,
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: '',
message
});
};
handleSubmit = (e) => {
e.preventDefault();
let curpass = this.state.cp_currentPassword
let newpass = this.state.cp_newPassword
this.setState({ loading: true });
this.props.changePassword(curpass, newpass, this.passwordUpdated)
}
passwordUpdated = () => {
this.setState({
message: 'Password changed Successfully.!',
open: true,
loading: false,
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: ''
});
};
render() {
const { classes, auth, authError } = this.props;
console.log(authError)
const { loading } = this.state;
const message = (
<span
id="snackbar-message-id"
dangerouslySetInnerHTML={{ __html: this.state.message }}
/>
);
if (!auth.uid) return <Redirect to='/signin' />
return (
<div>
{/* {authError ? this.openSnackbar({ message: '{authError}' }) : null} */}
{/* {authError && !this.state.open ? this.openSnackbar({ message: '{authError}' }) : null} */}
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card>
<CardHeader color="warning">
<h4 className={classes.cardTitleWhite}>Change Password</h4>
</CardHeader>
<form >
<GridContainer>
<GridItem xs={12} sm={12} md={6}>
<CardBody>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_currentPassword"
label="Current Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_currentPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_newPassword"
label="New Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_newPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_confirmPassword"
label="Confirm Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_confirmPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
</GridContainer>
</CardBody>
<CardFooter>
<Button color="warning" onClick={(e) => this.handleSubmit(e)} disabled={loading}>
{loading && <CircularProgress style={{ color: 'white', height: '20px', width: '20px', marginRight: '10px' }} />}
Change Password
</Button>
</CardFooter>
</GridItem>
</GridContainer>
</form>
</Card>
</GridItem>
</GridContainer>
<Snackbar
open={this.state.open}//{!!this.props.authError}//
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
message={message}
variant="error"
onClose={() => this.setState({ open: false, message: '' })}
action={
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={() =>
// clearAuthError
this.setState({ open: false, message: '' })
}
>
<CloseIcon className={classes.icon} />
</IconButton>
}
autoHideDuration={3000}
/>
</div>
)
}
}
const mapstateToProps = (state) => {
return {
auth: state.firebase.auth,
authError: state.authroot.autherr
}
}
const mapDispatchtoProps = (dispatch, getState) => {
return {
changePassword: (currentPassword, newPassword, passwordUpdated) => { dispatch(changePassword(currentPassword, newPassword, passwordUpdated)) },
}
}
export default compose(
withStyles(styles),
connect(mapstateToProps, mapDispatchtoProps)
)(ChangePassword);
In your handleSubmit function don't pass this.passwordUpdated
handleSubmit = (e) => {
e.preventDefault();
let curpass = this.state.cp_currentPassword
let newpass = this.state.cp_newPassword
this.setState({ loading: true });
this.props.changePassword(curpass, newpass, this.passwordUpdated) //Remove this.passwordUpdated from here
}
Instead of ComponentDidUpdate method you should use ComponentWillReceiveProps
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
if (this.props.authError !== nextProps.authError) {
// here you can make your textbox empty
this.setState({
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: ''
});
}
}
Note: In new version of react you must use static getDerivedStateFromProps() instead of componentWillReceiveProps.

React getting Maximum update depth exceeded error

I'm performing a change password, for authError I'm getting the following error..
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
changepassword.js
import React, { Component } from 'react'
import withStyles from "#material-ui/core/styles/withStyles";
import { Redirect } from 'react-router-dom'
import IconButton from '#material-ui/core/IconButton';
import { connect } from 'react-redux'
import { compose } from 'redux'
import {changePassword } from '../../store/actions/auth'
const styles = {
textField: {
fontSize: '5px'
},
};
class ChangePassword extends Component {
state = {
loading: false,
open:false,
message:'',
cp_currentPassword: '',
cp_newPassword: '',
cp_confirmPassword: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
openSnackbar = ({ message }) => {
this.setState({
open: true,
message,
});
};
handleSubmit = (e) => {
e.preventDefault();
let curpass=this.state.cp_currentPassword
let newpass=this.state.cp_newPassword
this.setState({loading:true});
this.props.changePassword(curpass,newpass)
this.openSnackbar({ message: 'Password changed Successfully.!' })
}
render() {
const { classes, auth, authError } = this.props;
const { loading } = this.state;
const message = (
<span
id="snackbar-message-id"
dangerouslySetInnerHTML={{ __html: this.state.message }}
/>
);
if (!auth.uid) return <Redirect to='/signin' />
return (
<div>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card>
<CardHeader color="warning">
<h4 className={classes.cardTitleWhite}>Change Password</h4>
</CardHeader>
<form >
<GridContainer>
<GridItem xs={12} sm={12} md={6}>
<CardBody>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_currentPassword"
label="Current Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_currentPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_newPassword"
label="New Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_newPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<TextField
id="cp_confirmPassword"
label="Confirm Password"
type="password"
fullWidth
className={classes.textField}
value={this.state.cp_confirmPassword}
onChange={this.handleChange}
margin="normal"
required={true}
/>
</GridItem>
</GridContainer>
</CardBody>
<CardFooter>
<Button color="warning" onClick={this.handleSubmit} disabled={loading}>
{loading && <CircularProgress style={{ color: 'white', height: '20px', width: '20px', marginRight: '10px' }} />}
Change Password
</Button>
</CardFooter>
</GridItem>
</GridContainer>
</form>
</Card>
</GridItem>
</GridContainer>
{authError ? this.openSnackbar({ message: '{authError}' }) : null}
<Snackbar
open={this.state.open}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
message={message}
variant="error"
onClose={() => this.setState({ open: false, message: '' })}
action={
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={() => this.setState({ open: false, message: '' })}
>
<CloseIcon className={classes.icon} />
</IconButton>
}
autoHideDuration={3000}
/>
</div>
)
}
}
const mapstateToProps = (state) => {
return {
auth: state.firebase.auth,
authError: state.authroot.autherr
}
}
const mapDispatchtoProps = (dispatch) => {
return {
changePassword: (currentPassword,newPassword) => { dispatch(changePassword(currentPassword,newPassword)) }
}
}
export default compose(
withStyles(styles),
connect(mapstateToProps,mapDispatchtoProps)
)(ChangePassword);
change password action
export const changePassword = (currentPassword, newPassword) => {
return (dispatch, getState, { getFirebase }) => {
const firebase = getFirebase();
console.log(currentPassword);
console.log(newPassword);
var user = firebase.auth().currentUser;
user.updatePassword(newPassword).then(() => {
console.log("Password updated!");
}).catch((error) => {
dispatch({ type: 'CHANGEPASSWORD_ERR', error })});
}
}
You are updating state here
{authError ? this.openSnackbar({ message: '{authError}' }) : null
that line runs multiple times, as it checks if there was auth error, if there was call openSnackBar, openSnackBar update state, which cause the component to re-render, after re-render the check happens again etc, which causes a loop. change it to the following and only call openSnackBar when state.open is false.
{authError && !this.state.open ? this.openSnackbar({ message: '{authError}' }) : null}
EDIT
remove authError from render and check in componentDidMount
componentDidMount = () => {
const { authError } = this.props;
if (authError) {
this.openSnackbar({ message: '{authError}' });
}
};

Resources