Component keeps looping - reactjs

In my componentDidMount() I am calling this.props.getPost(soloPost._id); in which case will use redux. This is working correctly, because if I console.log it I get my desired result. But componentDidMount() is looping continuously. I have a feeling i'm missing something on the component lifecycle
class PostItem extends Component {
constructor(props) {
super(props);
this.state = {
showCommentField: false
};
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState(prevState => ({
showCommentField: !prevState.showCommentField
}));
}
componentDidMount() {
const { soloPost } = this.props;
this.props.getPost(soloPost._id);
console.log(this.props.comments);
}
render() {
const { soloPost, classes } = this.props;
const postContent = (
<div>
<CommentFeed postId={soloPost._id} comments={soloPost.comments} />
<CommentForm postId={soloPost._id} />
</div>
);
return (
<div className={classes.section}>
<GridContainer justify="center">
<GridItem xs={12} sm={10} md={8}>
<hr />
<Card plain profile className={classes.card}>
<GridContainer>
<GridItem xs={12} sm={2} md={2}>
<CardAvatar plain profile>
<img src={soloPost.avatar} alt="..." />
</CardAvatar>
</GridItem>
<GridItem xs={12} sm={8} md={8}>
<h4 className={classes.cardTitle}>{soloPost.name}</h4>
<p className={classes.description}>{soloPost.text}</p>
</GridItem>
</GridContainer>
</Card>
</GridItem>
</GridContainer>
<GridContainer justify="center">
<Tooltip
id="tooltip-tina"
title="Reply to comment"
placement="top"
classes={{ tooltip: classes.tooltip }}
>
<Button
color="primary"
simple
className={classes.footerButtons}
onClick={this.onClick}
>
<Reply className={classes.footerIcons} /> Reply
</Button>
</Tooltip>
</GridContainer>
{this.state.showCommentField ? (
<GridContainer justify="center">
<GridItem xs={12} sm={5} md={5} lg={5}>
{postContent}
</GridItem>
</GridContainer>
) : (
<div />
)}
</div>
);
}
}
PostItem.defaultProps = {
showActions: true
};
PostItem.propTypes = {
soloPost: PropTypes.object.isRequired,
getPost: PropTypes.func.isRequired,
comments: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ comments: state.post.post });
export default connect(
mapStateToProps,
{ getPost }
)(withStyles(sectionBlogInfoStyle)(PostItem));
EDIT
I just tried this and it looped as well.
shouldComponentUpdate(nextProps, nextState) {
const { soloPost } = this.props;
if (nextProps !== this.props) {
this.props.getPost(soloPost._id);
return true;
}
if (nextState !== this.state) {
return true;
}
}

Related

React : Unable to set the values in the form

I want to set the values of the form if the edit is true. I am passing the editable state to true and passing the first element of the array but when the component is mounted it didn't show the 0th element values that were sent from the parent component to the child component.
Here's the code :
Milestone.js
import React, { Component } from 'react';
import "./milestone.css";
import MileStoneForm from './mileStoneForm'
import {LEFT_ARROW, ROUTES_PATH } from "../../constants";
export default class MilestoneComp extends Component {
constructor(props) {
super(props);
this.state = {
arr: [],
edit:true
}
}
backPage = () => {
this.props.history.push(ROUTES_PATH.CREATE_PROJECT)
}
handleData=(data)=>{
const {arr}=this.state
arr.push(data)
//localStorage.setItem('mileStoneData',JSON.stringify(arr))
console.log(arr)
}
render() {
return (
<div style={{ background: "#F3F3F3", minHeight: "100vh" }}>
<div className="header-div">
<img
src={LEFT_ARROW}
alt="back"
style={{ cursor: "pointer" }}
className="left-arrow-size left-back-btn-bt-mar"
onClick={this.backPage}
/>
</div>
<MileStoneForm arr={this.state.arr[0]} handleData={this.handleData} edit={this.state.edit}/>
</div>
)
}
}
MileStoneForm.js
import { InputBase,Grid,TextareaAutosize} from '#material-ui/core'
import React, { Component } from 'react'
import { DROP_D } from "../../constants";
import { PRIVATE_SwITCH,PUBLIC_SwITCH } from "../../constants";
import NormalButton from '../../common/component/normalButton';
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css';
class MileStoneForm extends Component {
constructor(props){
super(props)
this.state={
deliverable_name:"",
due_date:"",
deliverable_notes:"",
milestone_based_payment:false,
deliverable_name_error:"",
due_date_error:"",
deliverable_notes_error:"",
percent_rate:0,
percent_rate_error:""
}
}
componentDidMount(){
console.log('component did mount')
if(this.props.edit===true){
console.log('editable')
if(this.props.arr){
console.log('array')
this.setState({
deliverable_name:this.props.arr.milestoneName,
deliverable_notes:this.props.arr.description,
due_date:this.props.arr.dueDate,
milestone_based_payment:this.props.arr.isMilestoneBasedPayment,
percent_rate:this.props.arr.percentageRate
},()=>{
console.log('edit')
})
}
}
}
render() {
const {deliverable_name,deliverable_name_error,deliverable_notes,deliverable_notes_error,
due_date,due_date_error,milestone_based_payment,}=this.state
return (
<>
<div className="milestone">
<div className="milestone-header">ADD MILESTONE</div>
<Grid container className="milestone-deliverable-name-date">
<Grid item md={6} lg={6} xs={12}>
<div className="milestone-deliverable-name">DELIVERABLE NAME</div>
<InputBase
className={`milestone-input-deliverable-name`}
autoComplete={"off"}
placeholder={"MileStone Name"}
onChange={e=>this.handleChange(e,'deliverable_name')}
value={deliverable_name}
maxLength="100"
autoFocus={true}/>
{deliverable_name_error && (
<div className="input-error-style">{deliverable_name_error}</div>
)}
</Grid>
<Grid item md={6} lg={6} xs={12}>
<div className="milestone-due-date">
DUE DATE
</div>
<label>
<DatePicker
dateFormat="MM/dd/yyyy"
margin="normal"
selected={due_date}
placeholderText="Due Date"
onChange={date=>this.handleChangeDate(date,'due_date')}
maxDate={new Date()}
className={`milestone-input-due-date`}
/>
<img src={DROP_D} alt="drop down" style={{cursor:'pointer'}} className='dropdown-milestone'/>
</label>
{due_date_error && (
<div className="input-error-style">{due_date_error}</div>
)}
</Grid>
<Grid item md={12} lg={12} xs={12}>
<div className="milestone-notes-description">
<div className="milestone-deliverable-notes">DELIVERABLE NOTES</div>
<div className="milestone-description-notes">Add description below</div>
<TextareaAutosize className={`milestone-textarea-description`}
onChange={(e)=>this.handleChange(e,'deliverable_notes')}
value={deliverable_notes}/>
{deliverable_notes_error && (
<div className="input-error-style">{deliverable_notes_error}</div>
)}
</div>
</Grid>
<Grid item md={12} lg={12} xs={12}>
<div className="milestone-payment">MILESTONE BASED PAYMENT?</div>
{this.togglePayment()}
</Grid>
<Grid item md={12} lg={12} xs={12}>
{milestone_based_payment ?<>
<div className="percent-rate">PERCENT RATE</div>
<InputBase
className={`milestone-percent-rate`}
autoComplete={"off"}
placeholder={"20%"}
maxLength="100"
value={this.state.percent_rate}
onChange={(e)=>{this.handleChange(e, "percent_rate")}}
/>
{
this.state.percent_rate_error && (
<div className="input-error-style">{this.state.percent_rate_error}</div>
)
}
</> :''}
</Grid>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}>
<div className={milestone_based_payment?"milestone-button":"milestone-button-margin-btm"}>
<NormalButton
buttonValue="ADD"
className="btn-create-project flex-justify"
icon_color="black"
handleButtonAction={()=>this.handleSubmit()}
/>
</div>
</Grid>
</Grid>
</div>
</>
)
}
}
export default MileStoneForm
You are mutating your state with push, never do that. You have to create a new state object.
handleData=(data)=>{
const { arr }= this.state
const nextArr= {...arr, data}
//localStorage.setItem('mileStoneData',JSON.stringify(arr))
this.setState({arr: nextArr});
console.log(arr)
}
The console.log will show the new data anyway, since you are changing that object, but since the object reference will be the same (same object as before), it will not rerender.

Failed prop type: Material-UI: Either `children`, `image`, `src` or `component` prop must be specified in ForwardRef(CardMedia)

Whenever I'm trying to post new Screams in my page. I'm not getting the image, title and content of the card. I need to reload to get that.
It is saying failed prop types means after posting a scream I'm not getting the value from prop types but after a reload, everything seems to be fine. I don't know what is wrong is going. Please have a look at my code.
This is my Screams.js from where i'm showing my Screams
Scream.js
const styles = {
card: {
position: "relative",
display: "flex",
marginBottom: 20,
},
image: {
minWidth: 150,
},
content: {
padding: 25,
objectFit: "cover",
},
};
class Scream extends Component {
render() {
dayjs.extend(relativeTime);
const {
classes,
scream: {
body,
createdAt,
userImage,
userHandle,
screamId,
likeCount,
commentCount,
},
user: {
authenticated,
credentials: { handle },
},
} = this.props;
const deleteButton =
authenticated && userHandle === handle ? (
<DeleteScream screamId={screamId} />
) : null;
const likeButton = !authenticated ? (
<Link to="/login">
<MyButton tip="Like">
<FavoriteBorder color="primary" />
</MyButton>
</Link>
) : this.likedScream() ? (
<MyButton tip="Undo like" onClick={this.unlikeScream}>
<FavoriteIcon color="primary" />
</MyButton>
) : (
<MyButton tip="Like" onClick={this.likeScream}>
<FavoriteBorder color="primary" />
</MyButton>
);
return (
<Card className={classes.card}>
<CardMedia
image={userImage}
title="Profile Image"
className={classes.image}
/>
<CardContent className={classes.content}>
<Typography variant="h5" component={Link} to={`/users/${userHandle}`}>
{userHandle}
</Typography>
{deleteButton}
<Typography variant="body2" color="textSecondary">
{dayjs(createdAt).fromNow()}
</Typography>
<Typography variant="body1">{body}</Typography>
{likeButton}
<span> {likeCount} Likes </span>
<MyButton tip="comments">
<ChatIcon color="primary" />
</MyButton>
<span> {commentCount} Comments </span>
</CardContent>
</Card>
);
}
}
Scream.propTypes = {
likeScream: PropTypes.func.isRequired,
unlikeScream: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
scream: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
};
const mapStateToprops = (state) => ({
user: state.user,
});
const mapActionToProps = {
likeScream,
unlikeScream,
};
export default connect(
mapStateToprops,
mapActionToProps
)(withStyles(styles)(Scream));
As u can see i have userImage, userHandle and body of the card in the props and it is showing it on my page.
But after posting a new Scream, i'm not getting the image, userHandle and body of the new Scream unless and until reload it.
PostScream.js
class PostScream extends Component {
state = {
open: false,
body: "",
errors: {}
}
componentWillReceiveProps(nextProps) {
if(nextProps.UI.errors) {
this.setState({
errors: nextProps.UI.errors
});
}
if(!nextProps.UI.errors && !nextProps.UI.loading) {
this.setState({
body: "",
open: false,
errors: {}
})
}
}
handleOpen = () => {
this.setState({ open: true })
};
handleClose = () => {
this.props.clearErrors();
this.setState({ open: false, errors: {} });
};
handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault();
this.props.postScream({ body: this.state.body })
}
render() {
const { errors } = this.state;
const { classes, UI: { loading }} = this.props;
return (
<Fragment>
<MyButton onClick= { this.handleOpen } tip="Post a Scream!">
<AddIcon />
</MyButton>
<Dialog
open= { this.state.open }
onClose= { this.handleClose }
fullWidth
maxWidth = "sm"
>
<MyButton
tip="Close"
onClick={this.handleClose}
tipClassName={classes.closeButton}
>
<CloseIcon />
</MyButton>
<DialogTitle> Post a new Scream </DialogTitle>
<DialogContent>
<form onSubmit= { this.handleSubmit }>
<TextField
name="body"
type="text"
label="SCREAM!"
multiline
rows="3"
placeholder= "What's on your mind!"
error={ errors.body ? true: false }
helperText={ errors.body }
className={classes.TextField}
onChange={ this.handleChange }
fullWidth
/>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submitButton}
disabled={ loading }
>
Submit
{loading && (
<CircularProgress size={30} className={ classes.progressSpinner } />
)}
</Button>
</form>
</DialogContent>
</Dialog>
</Fragment>
)
}
}
PostScream.propTypes = {
postScream: PropTypes.func.isRequired,
clearErrors: PropTypes.func.isRequired,
UI: PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
UI: state.UI
});
export default connect(
mapStateToProps,
{ postScream, clearErrors }
)(withStyles(styles)(PostScream));
After posting a new Scream, i am getting Scream like this:-
I was having the same issue, I was able to fix it by adding component='img' to my CardMedia.
<CardMedia
image={userImage}
title="Profile Image"
className={classes.image}
component='img'
/>
Specifying the component as an img "Hides" the images since CardMedia is a div and your css is applied to the div. Add component = "div" to CardMedia.
<CardMedia
image={userImage}
title="Profile Image"
className={classes.image}
component='div'
/>

Material UI Switch onChange handlers are not working

I have implemented two material ui switches that are supposed to update properties in the state, however it seems their onChange handlers are not working because the properties are not getting updated(however I am able to flip the switches' controls). The expected behaviour would be when I flip on / off the switch and click save, the properties in the state would change. For reference to the switch api, visit this link - https://material-ui.com/components/switches/#Switches.js
Here is what i have implemented.
export class SwitchNotifications extends React.Component {
constructor(props) {
super(props);
this.state = {
email_notification_enabled: false,
app_notification_enabled: false
};
}
componentDidMount() {
const token = localStorage.getItem("token");
let detail = details(token);
this.props.dispatch(fetchProfile(detail.username));
}
componentWillReceiveProps(nextProps) {
this.setState({
email_notification_enabled: nextProps.profile.email_notification_enabled,
app_notification_enabled: nextProps.profile.app_notification_enabled
});
}
handleChangeEmailNotification = event => {
this.setState({ email_notification_enabled: event.target.checked });
};
handleChangeAppNotification = event => {
this.setState({ app_notification_enabled: event.target.checked });
};
handleSubmit = event => {
event.preventDefault();
const new_profile = {
email_notification_enabled: this.state.email_notification_enabled,
app_notification_enabled: this.state.app_notification_enabled
};
let data = { profile: new_profile };
this.props.dispatch(updateProfileAction(this.state.username, data));
};
render() {
const {
app_notification_enabled,
email_notification_enabled
} = this.state;
return (
<div>
<br />
<div className="containers">
<div className="upload-profile-form edit-upload-course-form">
<img id="member-img" src={image} />
<br />
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControlLabel
control={
<Switch
checked={app_notification_enabled}
onChange={this.handleChangeAppNotification}
value={app_notification_enabled}
inputProps={{ "aria-label": "secondary checkbox" }}
/>
}
label="App notifications"
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Switch
checked={email_notification_enabled}
onChange={this.handleChangeEmailNotification}
value={email_notification_enabled}
color="primary"
inputProps={{ "aria-label": "primary checkbox" }}
/>
}
label="Email notifications"
/>
</Grid>
<Grid item xs={12}>
<button
onClick={this.handleSubmit}
className="submit-profile-button"
>
{" "}
SAVE
</button>
</Grid>
</Grid>
</div>
</div>
</div>
);
}
}

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}' });
}
};

How to render react component based on event accurately?

When event is fired, I got a warning message. What is the best approach to update component.Help please.
Warning: Can only update a mounted or mounting component. This usually
means you called setState, replaceState, or forceUpdate on an
unmounted component. This is a no-op.
import Table from "./Table";
import Tree1 from "./Tree1";
class ComponentView extends Component {
constructor(props) {
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
this.state = {
viewState: <Table />
};
}
onButtonClick(event) {
event.preventDefault()
const btnValue = event.target.value;
switch (btnValue) {
case 'Table':
this.setState({ viewState: <Table /> });
break;
case 'Tree1':
this.setState({ viewState: <Tree1 /> });
break;
default:
break;
}
}
render() {
return (
<div className="animated fadeIn">
<Row>
<Col xs="12" sm="12" lg="12">
<Card>
<CardHeader>
<Button onClick={this.onButtonClick} color="primary" size="sm" value="Table" >Table</Button>
<Button onClick={this.onButtonClick} color="secondary" size="sm" value="Tree1">Tree1</Button>
</CardHeader>
<CardBody className="pb-0" style={{ height: '500px' }}>
{this.state.viewState}
</CardBody>
</Card>
</Col>
</Row>
</div>
)
}
}
You probably need to distill what your problem is - your code runs without warnings.
window.onload = () => {
console.clear();
const Row = p => <div>{p.children}</div>
const Col = p => <div>{p.children}</div>
const Card = p => <div>{p.children}</div>
const CardHeader = p => <div>{p.children}</div>
const CardBody = p => <div>{p.children}</div>
const Button = p => <button {...p} />
const Table = () => <span>Table</span>;
const Tree1 = () => <span>Tree</span>;
class ComponentView extends React.Component {
constructor(props) {
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
this.state = {
viewState: <Table />
};
}
onButtonClick(event) {
event.preventDefault()
const btnValue = event.target.value;
switch (btnValue) {
case 'Table':
this.setState({ viewState: <Table /> });
break;
case 'Tree1':
this.setState({ viewState: <Tree1 /> });
break;
default:
break;
}
}
render() {
return (
<div className="animated fadeIn">
<Row>
<Col xs="12" sm="12" lg="12">
<Card>
<CardHeader>
<Button onClick={this.onButtonClick} color="primary" size="sm" value="Table" >Table</Button>
<Button onClick={this.onButtonClick} color="secondary" size="sm" value="Tree1">Tree1</Button>
</CardHeader>
<CardBody className="pb-0" style={{ height: '500px' }}>
{this.state.viewState}
</CardBody>
</Card>
</Col>
</Row>
</div>
)
}
}
const d = document.createElement('div')
document.body.appendChild(d)
ReactDOM.render(<ComponentView />, d)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>

Resources