Material UI Switch onChange handlers are not working - reactjs

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

Related

React.js: conditional rendering does not load child component

I need to render the child component during conditional rendering. It is not displayed in the current version.
If you insert into the html markup, then there are no errors, the component is rendered normally.
What could be my mistake with conditional rendering?
Parent component:
export default class App extends Component {
data = {
email: "a#b.net",
password: "adc"
}
state = {
email: "",
password: ""
}
emailChange=(e)=>{
this.setState({email: e.target.value});
}
passwordChange=(e)=>{
this.setState({password: e.target.value});
}
buttonSubmit=(e)=>{
let p=this.state.email===this.data.email
&& this.state.password===this.data.password ? <div><Page1/></div> : alert('poi');
e.preventDefault()
}
render() {
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<form noValidate autoComplete="off"
onSubmit={this.buttonSubmit}>
<div style={{marginTop:"150px"}}>
<Grid container spacing={2}>
<Grid item xs={12} >
<TextField
id="outlined-name"
label="e-mail"
variant="outlined"
value={this.state.email}
onChange={this.emailChange}/>
</Grid>
<Grid item xs={12} >
<TextField
className="MuiInput-input"
id="outlined-name"
label="password"
variant="outlined"
value={this.state.password}
onChange={this.passwordChange}/>
</Grid>
<Grid item xs={12}>
<Button
style={{width:'210px'}}
type="submit"
fullWidth
variant="contained"
color="primary"
>
Enter
</Button>
</Grid>
</Grid>
</div>
</form>
</Container>
);
}
}
Child component that is not rendered during conditional rendering:
const Page1 =()=>{
return (
<div style={{height: "100vh"}}>
<Header/>
<Grid container spacing={3}>
<Grid item xs={3}>
<Paper><ButtonPage/></Paper>
</Grid>
<Grid item xs={12}>
<Paper><ButtonWindow /></Paper>
</Grid>
</Grid>
</div>
);
}
You're just assigning your child component conditionally to a local variable p, that you never render. I'd change your logic like this:
buttonSubmit=(e)=>{
let p=(this.state.email===this.data.email &&
this.state.password===this.data.password) ?
<div><Page1/></div>
:
null;
this.setState({p: p});
e.preventDefault()
}
render() {
...
{this.state.p}
}

is there any way to disable send button until message got typed in the message field without onchange method in reactjs

I'm new to react, My objective is to disable send button until message got typed in the message field or to show some popup without writing anything in textfield and clicking on send button. For Disabling - I've tried by giving onChange method but due to onchange method input field is getting laggy. That's why i'm using Id. Can anyone suggest me any way of disabling the button ?
Here is the code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{ isSender: true, content: "hello" },
{ isSender: false, content: "hello1" },
{ isSender: true, content: "hello2" },
{ isSender: false, content: "hello3" },
{ isSender: true, content: "hello4" },
{ isSender: false, content: "hello5" },
{ isSender: true, content: "hello6" },
{ isSender: false, content: "hello7" }
],
msg: ""
};
}
componentDidMount() {
this.scrollToBottom();
}
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
};
componentDidUpdate() {
this.scrollToBottom();
}
handleSubmit = e => {
e.preventDefault();
let text = document.getElementById("text").value;
console.log(text);
};
render() {
const { classes } = this.props;
return (
<Card>
<CardHeader
avatar={<Avatar aria-label="recipe">S</Avatar>}
title={
<>
<InputBase placeholder="Search Google Maps" margin="normal" />
<IconButton type="submit" aria-label="search">
<SearchIcon />
</IconButton>
</>
}
/>
<Divider />
<CardContent
style={{ overflow: "scroll" }}
className={classes.contentHeight}
id="chatList"
>
<div>
<Message isSender content="Hello" />
{this.state.data.map(item => {
if (item.isSender) {
return <Message isSender content={item.content} />;
}
return <Message content={item.content} />;
})}
</div>
<div
style={{ float: "left", clear: "both" }}
ref={el => {
this.messagesEnd = el;
}}
/>
</CardContent>
<Divider />
<CardActions>
<Paper className={classes.contentPaper}>
<Input
margin="dense"
className={classes.input}
placeholder="Enter a Message"
disableUnderline
name="msg"
id="text"
/>
<Button onClick={this.handleSubmit}>Send</Button>
</Paper>
</CardActions>
</Card>
);
}
}
Either the send button needs to disable or while clicking on the send button without typing anything in input field - it should show snackbar at the top of the textfield as "Enter something".
Can anyone please help me in this?
Here is the whole code:
You should update state on onChange for input
<Input
margin="dense"
className={classes.input}
placeholder="Enter a Message"
disableUnderline
name="msg"
id="text"
onChange={e => this.setState({ msg: e.target.value })}
/>
And disable attribute to Button
<Button onClick={this.handleSubmit} disabled={!this.state.msg.length}>
Send
</Button>
Try to avoid document.getElementById("text").value in react, it's better to use state or ref

React Router V4 <Redirect> Doesn't Trigger on Form Submission

I have a route component that redirects in its render() based on whether or a submit form in the component’s modal has been switched to true.
Home.JS
import { BrowserRouter as Router, Route, Link, Redirect } from "react-router-dom";
class Home extends Component {
constructor(props) {
super(props)
this.state = {
dialogueOpen: false,
loginMessage: '',
name: '',
password: '',
newUsername: '',
newPassword: '',
returnedUser: undefined,
registrationMessage: '',
redirectToSignupSuccess: false
}
this.checkUniqueUsername = this.checkUniqueUsername.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleLogin = this.handleLogin.bind(this)
this.handleOpen = this.handleOpen.bind(this)
this.handleRegistration = this.handleRegistration.bind(this)
}
This is the method to switch redirectToSignupSuccess to true.
handleRegistration = event => {
//check that username and passwords aren't empty strings
if (this.state.newUsername.length() > 0 && this.state.newPassword.length() > 0) {
//check that the new username isn't already in the database
this.checkUniqueUsername(this.state.newUsername)
.then(isUnique => {
console.log(isUnique)
if (isUnique === true) {
//console.log("the username is unique")
axios.post('http://localhost:4242/createuser', {
username: this.state.newUsername,
password: this.state.newPassword
})
.then( (response) => {
console.log(response)
this.setState({redirectToSignupSuccess: true}, () => {
console.log(this.state.redirectToSignupSuccess)
})
//this.handleClose()
})
.catch((error) => {
console.log(error);
});
}
})
}
else {
this.setState({registrationMessage:
"Sorry, but we need a username and password for you to sign up"})
}
event.preventDefault();
}
The render()
render() {
let isAuthed = localStorage.getItem("authorized");
let redirectToSignupSuccess = this.state.redirectToSignupSuccess
if (isAuthed === "true") {
return (<Redirect to='/inner' />)
}
if (redirectToSignupSuccess === true) {
return (<Redirect to='/signupsuccess' />)
}
return (
<div>
<h1 className="green home-logo">APPLi</h1>
<div className="login-message">
<h4>{this.state.loginMessage}</h4>
</div>
<form onSubmit={this.handleLogin} style={{ padding: 8 }}>
<Grid container spacing={16} direction="row" justify="center">
<Grid item xs={2} >
<FormControl margin="normal">
<InputLabel htmlFor="component-simple">Name</InputLabel>
<Input id="component-simple" value={this.state.name} onChange={this.handleChange('name')} />
</FormControl>
</Grid>
<Grid item xs={2} >
<FormControl margin="normal">
<InputLabel htmlFor="component-simple">Password</InputLabel>
<Input id="component-simple" value={this.state.password} onChange={this.handleChange('password')} />
</FormControl>
</Grid>
<Grid item xs={12}>
<Button color="secondary" label="submit" type="Submit" variant="contained" >Log In</Button>
</Grid>
</Grid>
</form>
<Button onClick={this.handleOpen} color="primary" variant="contained">Sign Up</Button>
<Dialog open={this.state.dialogueOpen} onClose={this.handleClose}>
<h2>Register for an account</h2>
<form onSubmit={this.handleRegistration} style={{ padding: 8 }}>
<Grid container spacing={16} justify="center">
<Grid item xs={6} >
<FormControl required={true}>
<InputLabel htmlFor="component-simple">Your Username</InputLabel>
<Input id="component-simple" value={this.state.newUsername} onChange={this.handleChange('newUsername')}/>
</FormControl>
</Grid>
<Grid item xs={6} >
<FormControl required={true}>
<InputLabel htmlFor="component-simple">Your Password</InputLabel>
<Input id="component-simple" value={this.state.newPassword} onChange={this.handleChange('newPassword')} />
</FormControl>
</Grid>
<Grid item xs={12}>
<p>{this.state.registrationMessage}</p>
<Button color="secondary" label="submit" type="Submit" variant="contained">Create Account</Button>
</Grid>
</Grid>
</form>
</Dialog>
</div>)
Routes in App.js
render() {
return (
<Router>
<div className="App">
<div className="portfolioBar">
<div className="arrowContainer">
<FontAwesomeIcon icon="arrow-left" color="black" size="3x" />
</div>
</div>
<Route exact path="/" render={(props) =>
<Home authorizeUser={this.authorizeUser} setCurrentUser={this.setCurrentUser} currentUser={this.state.currentUser} {...props} />
}/>
<Route path="/signupsuccess" component={SignupSuccess} />
<Route exact path="/inner" render={(props) =>
<UserView currentUser={this.state.currentUser} currentUserId={this.state.currentUserId} handleLogout={this.handleLogout} {...props} />
}/>
</div>
</Router>
);
}
}
export default App;
Normally, when the page’s form is submitted, the api should send the new user information to the database, and they should redirect to /signupsuccess. Instead, what happens is that the page refreshes, and the api call to the database isn’t made. I’ve set the event.preventDefault() on the form, but instead the console flashes a turn of error messages, and the page resets. What could I be missing?

Reusing multiple instances of react component with different props

So I have a child component that I want to render multiple instances of in a parent container component. Passing in different props to each so they display differently.
What is happening is that they are both being rendered with the last instance of the props in the script being read into both instances. Thus the both components below end up with placeHolder==='Describe yourself'
Is there a work around for this so that they will each be injected with their props in turn exclusively?
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder='Describe yourself'
label='Bio'
change={this.handleBio}
/>
ButtonMode
class ButtonMode extends Component {
constructor(props){
super(props)
this.state = {
input:''
}
this.handleInput = this.handleInput.bind(this);
this.handle = this.handle.bind(this);
}
handleInput(val){
this.setState({input:val})
};
handle() {
this.props.change(this.state.input);
};
render(){
const { classes } = this.props;
return (
<div>
<Button
className={classes.button}
onClick={this.props.modalOpen}
>Update
</Button>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.props.open}
onClose={this.props.handleClose}
>
<div className={classes.paper}>
<TextField
id="filled-textarea"
label={this.props.label}
placeholder={this.props.placeHolder}
multiline
className={classes.textField}
onChange={(e)=>{this.handleInput(e.target.value)}}
rows= '4'
/>
<Button
onClick={this.handle}
className={classes.button}
color="secondary">Submit</Button>
</div>
</Modal>
</div>
)
}
}
Then I used it like that
class UserCard extends Component {
constructor(props){
super(props);
this.state = {
tempPro:'',
open: false,
profilePicture:''
}
this.modalOpen = this.modalOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handlePicture = this.handlePicture.bind(this);
}
// componentDidMount(){
// const {userId, profilePic} = this.props;
// this.setState({profilePicture:profilePic});
// // axios.get(`/api/profile/${userId}`).then(res=>{
// // let {profilePic} = res.data[0];
// // this.setState({profilePic})
// // })
// }
handlePicture(val){
this.props.changePic(val);
this.setState({open:false});
};
handleBio(val){
this.setState({open:false});
};
handleClose(){
this.setState({open: false});
};
modalOpen(){
this.setState({open:true});
};
render() {
const { classes } = this.props;
const {stories} = this.props;
let storyShow = stories.map((story,id) => {
return(
<div value={story.story_id}>
<h3>{story.title}</h3>
<ul className={classes.background}>
<li>{story.description}</li>
<li>{story.is_complete}</li>
</ul>
</div>
)
});
return (
<div className={classes.rootD}>
<Grid container>
<Grid className={classes.itemFix} >
<Card className={classes.card}>
<CardMedia
className={classes.media}
image={this.props.proPic}
title={this.props.userName}
/>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
</div>
<CardHeader
className={classes.titles}
title={this.props.userName}
subheader="Somewhere"
/>
<CardHeader className={classes.titles} title='Bio' />
<CardContent className={classes.background}>
<Typography className={classes.bio} paragraph>
{this.props.bio}
</Typography>
</CardContent>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder='Describe you how you want'
label='Bio'
change={this.handleBio}
/>
</div>
</Card>
</Grid>
<Grid className={classes.itemFixT}>
<Card className={classes.card}>
<CardContent>
<CardHeader
className={classes.titles}
title='Works'/>
<Typography paragraph>
<ul>
{storyShow}
</ul>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</div>
);
}
}
UserCard.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state){
const {userId, profilePic} = state;
return {
userId,
profilePic
}
}
export default connect(mapStateToProps,{})(withStyles(styles)(UserCard));
I had a similar issue where I was trying to pass different functions to the children components. I had a UploadFile component that contained an <input/> and a <Button/> from material-ui, and I wanted to reuse this component multiple times throughout a page, as the user has multiple files to upload, and in order to save the files, I needed callback functions in the main page.
What I had to do, was give each child component <UploadFile/> in my case, and <ButtonMode/> in your case, a unique id passed in as a prop, since otherwise, the top level page cannot tell each reference to the child component apart from any others.
The code of the child component:
function UploadFile({ value, handleFile }) {
const classes = useStyles();
return (
<>
<input
accept=".tsv,.fa,.fasta"
className={classes.input}
id={value}
type="file"
style={{ display: 'none' }}
onChange={e => handleFile(e.target.files[0])}
/>
<label htmlFor={value}>
<Button
variant="contained"
color='default'
component="span"
startIcon={<CloudUploadIcon />}
className={classes.button}>
Upload
</Button>
</label>
</>
);
}
The usage of this component in the parent (handleFile is the function I am passing in and is defined above in the parent component):
<UploadFile value='prosite' handleFile={handlePrositeFile} />
<UploadFile value='pfam' handleFile={handlePfamFile} />
I spent an embarrassingly long time on a similar issue. I tried all sorts of JS debugging and even re-read the entire concept of closure :)
This is was my culprit: <TextField id="filled-textarea" ... />
i.e. the id is static. If we have multiple instances of the same id on one page, we have a problem.
Make id dynamic, e.g. <TextField id={this.props.label} ... />
I was using the same state for both modals and in each instance of handleOpen() it was only ever opening the last instance of modal in the script.

Putting a customised radio button component inside a Radio Group in Material UI

I want to have a list of radio buttons, with one option being a freestyle 'Other' text box that lets the user enter their own text.
Here I have a working sandbox of everything I want to do:
https://codesandbox.io/s/r4oo5q8q5o
handleChange = event => {
this.setState({
value: event.target.value
});
};
selectItem = item => {
this.setState({
selectedItem: item
});
};
handleOtherChange = event => {
this.setState({
otherText: event.target.value
});
this.selectItem(
//Todo put in right format
this.state.otherText
);
};
focusOther = () => {
this.setState({
value: "Other"
});
this.selectItem(this.state.otherText);
};
render() {
const { classes, items } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<Typography>
{" "}
Selected item is: {JSON.stringify(this.state.selectedItem)}
</Typography>
<FormControl component="fieldset" fullWidth>
<RadioGroup value={this.state.value} onChange={this.handleChange}>
{items.map(v => (
<FormControlLabel
value={v.name}
control={<Radio />}
label={v.name}
key={v.name}
onChange={() => this.selectItem(v)}
/>
))}
<FormControlLabel
value="Other"
control={<Radio />}
label={
<TextField
placeholder="other"
onChange={this.handleOtherChange}
onFocus={this.focusOther}
/>
}
onChange={() => this.selectItem(this.state.otherText)}
/>
</RadioGroup>
</FormControl>
</div>
);
}
}
Now what I want to do is make the 'Other' text box its own component.
Here's my attempt:
https://codesandbox.io/s/ryomnpw1o
export default class OtherRadioButton extends React.Component {
constructor() {
super();
this.state = {
text: null
};
}
handleTextChange = event => {
this.setState({
text: event.target.value
});
this.props.onChange(this.state.text);
};
focusOther = () => {
this.props.onFocus(this.props.value);
this.props.onChange(this.state.text);
};
render() {
return (
<FormControlLabel
value={this.props.value}
control={<Radio />}
label={
<TextField
placeholder="other"
onChange={this.handleTextChange}
onFocus={this.focusOther}
/>
}
onChange={this.focusOther}
/>
);
}
}
Used with:
<OtherRadioButton
value="Other"
onFocus={v => this.setState({ value: v})}
onChange={v => this.selectItem(v)}
/>
As you can see - the value of the free text is propagating back fine - but the RadioGroup seems like it's not aware of the FormGroupLabel's value.
Why is this, and how would I solve this?
You can check the RadioGroup source code here.
And I have written my own code to better illustrate how it can be fixed. See here: https://codesandbox.io/s/mz1wn4n33j
RadioGroup creates some props to its FormControlLabel/RadioButton children. By creating your own customized radio button in a different component, these props are not passed to FormControlLabel/RadioButton.
You can fix these by passing the props to your FormControlLabel in your custom RadioButton.
<FormControlLabel
value={this.props.value} //Pass this
onChange={this.props.onChange} //Pass this one too
checked={this.props.checked} //Also this
control={<Radio name="gender" />}
label={
<TextField
id="standard-bare"
defaultValue={this.props.defaultValue}
margin="normal"
onChange={this.props.onTextChange}
/>
}
/>

Resources