Best way to set state of object using dynamic key name? - reactjs - javascript

I have a react state like:
this.state = {
formInputs: {
username: '',
password: '',
passwordConfirm: '',
},
handleChange = () => {
const {target: {name, value}} = event;
this.setState({
[name as keyof formInputs]: value
});
}
};
How can I change this line ( [name as keyof formData]: value) to a JavaScript instead of Typescript?

We can use Computed property names concept to compute the object property name dynamically. For that we need to put the expression inside [].
When you need to handle multiple controlled input elements, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name.
For your state
this.setState({
formInput: {
...this.state.formInput,
[event.target.name]: event.target.value
}
})
Sandbox for your reference: https://codesandbox.io/s/react-basic-example-p7ft8
import React, { Component } from "react";
export default class Login extends Component {
state = {
formInputs: {
email: "",
password: ""
}
};
handleOnChange = event => {
this.setState({
formInput: {
...this.state.formInput,
[event.target.name]: event.target.value
}
});
};
render() {
return (
<form>
<label>Email</label>
<input type="text" name="email" onChange={this.handleOnChange} />
<label>Password</label>
<input type="password" name="password" onChange={this.handleOnChange} />
</form>
);
}
}

You can directly use Bracket_notation
[name]: value
In your case, { formInput: { username:"", password: "" }
this.setState({
formInput: {
...this.state.formInput,
[name]: value
}
});

I think you can initialize your formObject as empty json and
this.state = {formInput: {}}
Later onChange you can set the value something like
this.setState({formInput[event.target.name]: event.target.value})
and conditionaly check if (this.state.formInput.username) ? this.state.formInput.username : '' to value

Related

How to change the state in react with a form

I have a form that is supposed to update the initial state, I've followed many tutorials and my code looks the same as them but for some reason it doesn't update the state.
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState({title})
}
onBodyChange(e) {
const body = e.target.value;
this.setState({body})
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState(() => ({ error: "Please fill in all gaps"}))
} else {
this.setState(() => ({ error: "" }))
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
When I check the current state in the React developer tools it shows that the state remains the same and I don't know why because there are not errors in the log.
I'm working with webpack, babel and react.
////////////////////
EDITED
////////////////////
I edited my code following the suggestions you guys gave me but still it doesn't work. An alert is supposed to appear when submitted the form but that doesn't get fired either, so I believe that non of the functions are getting fired.
This is my edited code:
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
}
onBodyChange(e) {
const body = e.target.value;
this.setState((prevState) => {
return {
...prevState,
body
};
});
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState((prevState) => {
return {
...prevState,
error: "Please fill in all gaps"
};
});
} else {
this.setState((prevState) => {
return {
...prevState,
error: ""
};
});
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
You should try binding the event handlers in the constructor, because it seems like this within those event handling functions could be undefined. The React documentation outlines why the binding is necessary, and here's another useful page on handling forms in React.
constructor(props) {
super(props);
...
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmitChange = this.onSubmitChange.bind(this);
}
An alternative to binding in the constructor would also be to use arrow functions for your event handlers which implicitly bind this.
class Create extends Component {
...
onTitleChange = () => { ... }
onBodyChange = () => { ... }
onSubmitChange = () => { ... }
}
EDIT: Can't comment on your post since my reputation is too low, but it seems like there's a typo in the changes you just made.
this.onSubmitC = this.onSubmit.bind(this) should be changed to
this.onSubmit = this.onSubmit.bind(this)
React's setState() accepts an object, not a function. So change your onSubmit() to this:
if(!this.state.title || !this.state.body){
this.setState({ error: "Please fill in all gaps"})
} else {
this.setState({ error: "" })
// Send info to the main page
alert(this.state.title);
}
It better to use the previous state and only update the required (input value) values.
in your case you are replacing the existing state (whole object) just with the title and the same goes for body onBodyChange
onTitleChange () {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
};
<

React-Redux: State always one input behind

I've created a class based component that renders an input field. I need the global state to update while the user types into the input. The issue is that the global state is always one step (render?) behind from what's actually in the input field. For example, when I write “winners” in the input, the state is “winner” instead. How can I fix this?
Component
class TeamCardT1 extends Component {
constructor(props) {
super(props);
// local state
this.state = {
team_one_name: "Team One",
};
// bind events
this.handleName = this.handleName.bind(this);
};
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
});
this.props.handleSubmit(this.state);
};
render() {
const { team_one_name } = this.state;
return (
<>
<div className="teamCard_container">
<input
type="text"
id="team_one_name"
name="team_one_name"
value={team_one_name}
onChange={this.handleName}
maxLength="35"
minLength="2"
className="teamCard_teamName" />
<PlayersCardT1 />
<ScoreCard />
</div>
</>
)
};
}
index.js for the component
const mapStateToProps = ({ team_one_name }) => {
return {
team_one_name,
};
};
// Dispatch
const mapDispatchToProps = dispatch => {
return {
handleSubmit: (data) => { dispatch(updateTeamOneName(data)) }
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamCardT1);
You handleSubmit with the previous state, change to the current value.
handleName = e => {
this.setState({ team_one_name: e.target.value });
this.props.handleSubmit(e.target.value);
};
Notice that you already have a shallow merge with setState so you don't need to destruct this.state.
state is one step behind because you should call the prop function as a setState callback by this way the prop function will call just after the state set.
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
}, () => {
this.props.handleSubmit({value: e.target.value});
});
};

How to Pass Key value from inputText change in ReactJs in typescript?

I am using switch case for comparison for object key with string in below code:
import { TextField, Button } from "#material-ui/core";
import React, { Component, ReactNode } from "react";
import classes from "./Contact.module.scss";
class contactForm extends Component {
state = {
contactForm: {
name: "",
email: "",
message: "",
phone: ""
}
};
render(): ReactNode {
return (
<form className={classes.ArticleBody}>
<div className={classes.ContactForm}>
<TextField
value={this.state.contactForm.name}
onChange={event => this._inputChangeHandler(event, "name")}
label="Full Name"
required
/>
<TextField
value={this.state.contactForm.email}
onChange={event => this._inputChangeHandler(event, "email")}
type="Email"
label="Email"
required
/>
<TextField
value={this.state.contactForm.phone}
onChange={event => this._inputChangeHandler(event, "phone")}
type="phone"
label="Phone Number"
required
/>
<TextField
type="textarea"
value={this.state.contactForm.message}
label="Comment/Message"
rows="4"
onChange={event => this._inputChangeHandler(event, "message")}
multiline
required
/>
</div>
<div className={classes.Submit}>
<Button type="submit" onClick={this._submitContactForm}>
Submit
</Button>
</div>
</form>
);
}
private _inputChangeHandler = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, key: string) => {
const updatedFormData = { ...this.state.contactForm };
switch (key) {
case "email":
updatedFormData.email = event.target.value;
break;
case "phone":
updatedFormData.phone = event.target.value;
break;
case "message":
updatedFormData.message = event.target.value;
break;
case "name":
updatedFormData.name = event.target.value;
break;
}
this.setState({ contactForm: updatedFormData });
};
private _submitContactForm = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.preventDefault();
console.log(this.state.contactForm);
};
}
export default contactForm;
I don't want to compare my object keys with switch case. Is there any generic approach for changing values on input change for the defined state?
e.g.: in below code I am trying to match key from parameter in method _inputChangeHandler but it throws error
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type '{ name: string; email: string;
message: string; phone: string; }'
const updatedFormData = { ...this.state.contactForm };
updatedFormData[key] = event.target.value;
this.setState({ contactForm: updatedFormData });
Thanks
try like this
this.setState({ contactForm: {...this.state.contactForm, [key]: event.target.value} });
#Ajay Verma
you could set the name attribute of the TextField and then you can get the key from the event.
like this
...
private _inputChangeHandler = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const key = event.target.name;
...
<TextField
value={this.state.contactForm.phone}
name="phone"
onChange={this._inputChangeHandler}
type="phone"
label="Phone Number"
required
/>
...
The TextField has a name prop.
When you need to handle multiple controlled input elements, you can
add a name attribute to each element and let the handler function
choose what to do based on the value of event.target.name. React
Docs
For example:
<TextField
name="name"
value={this.state.contactForm.name}
onChange={this._inputChangeHandler}
label="Full Name"
required
/>
....
public _inputChangeHandler(e) {
this.setState((prevState) => {
return {
contactForm: {
...prevState.contactForm,
[e.target.name] : e.target.value
}
}
});
}
A general inputChangeHandler can be:
private _inputChangeHandler = (
event: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement
>,
key: string
) => {
this.setState({
contactForm: {
...this.state.contactForm,
[key]: event.target.value,
},
});
};
Your original code would work but you have to give typescript a hint when using bracket notation to access a property:
const contactForm: {
[key: string]: string;
} = {
...this.state.contactForm,
};
contactForm[key] = event.target.value;
this.setState({
contactForm,
});

Modify data in children component and make it available in the parent as props

I am trying to modify some state parameters inside a child component and then pass it for use in the parent.
I have a state object called driver. Inside of my EditDriver component, I am calling ProfileTab and passing it the driver object, with fields like firstName, and more. Inside of the ProfileTab is where I do the actual modification. Inside the parent component EditDriver is where I need to send this data to the server in the updateDriver function.
//...imports
class EditDriver extends Component {
constructor(props) {
super(props);
this.state = {
driver: {
firstName: '',
},
};
this.updateDriver = this.updateDriver.bind(this);
this.driverOnChange = this.driverOnChange.bind(this);
}
updateDriver() {
this.props.updateDriver(this.state.driver);
}
driverOnChange(data) {
this.setState({
driver: data
});
}
render() {
return (
<ViewContainer
fullWidth
title="Driver"
toolbarRight={
<Button
onClick={this.updateDriver}
>
Save
</Button>
}
>
<ProfileTab match={this.props.match} driver={this.state.driver} driverOnChange={this.driverOnChange} />}
</ViewContainer>
);
}
}
const mapDispatchToProps = dispatch => ({
updateDriver: driver => dispatch(updateDriver(driver))
});
export default connect(
null,
mapDispatchToProps,
)(withStyles(styles)(EditDriver));
and the ProfileTab code looks like this:
class ProfileTab extends Component {
constructor(props) {
super(props);
this.state = {
driver: {
firstName: '',
},
};
this.handleDriverInputChange = this.handleDriverInputChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.driver && !nextProps.isFetching) {
this.setState({
driver: {
...this.state.driver,
...nextProps.driver
}
});
}
}
handleDriverInputChange(event) {
const target = event.target;
const value = target.type == 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
driver: {
...this.state.driver,
[name]: value
}
});
}
render() {
const {
driver,
} = this.state;
return (
<div>
<TextField
name="firstName"
label="firstName"
margin="normal"
type="text"
onChange={this.handleDriverInputChange}
value={driver.firstName}
/>
</div>
);
}
}
ProfileTab.propTypes = {
driver: PropTypes.object.isRequired,
driverOnChange: PropTypes.func.isRequired,
};
export default ProfileTab;
The driverOnChange function does not actually set the state of the driver parameter in the ProfileTab. What would be the most efficient way to render the ProfileTab in the EditDriver component and have the changed parameter firstName available?
Just call driverOnChange in this method inside ProfileTab component
handleDriverInputChange(event) {
const target = event.target;
const value = target.type == 'checkbox' ? target.checked : target.value;
const name = target.name;
this.props.driverOnChange(value);
this.setState({
driver: {
...this.state.driver,
[name]: value
}
});
}
You've binded driverOnChange to EditDriver component therefore I think this in driverOnChange would refer to it even when it is being called inside ProfileTab component

State of React.js component is not updated when browser auto-completes username

I have the following component in React:
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {username: '', password: '', redirectToReferrer: false};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const value = event.target.value;
const name = event.target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
console.log('A name was submitted: ' + this.state.username);
Auth.authenticate(this.state.username, this.state.password, () => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<form id='loginForm'>
<input type="text" name="username" onChange={this.handleChange} />
<input type="password" name="password" onChange={this.handleChange} />
<button onClick={this.handleSubmit}>Log in</button>
</form>
</div>
)
}
}
When I use the browser auto-complete feature (instead of typing 'admin' I type just 'a' and let browser to fill the rest) the component's state is not update and it submits incorrect value. When I type the username/password all by hand it works correctly. How can I fix this? It's pretty common use case...
It looks like that some browsers have a bug.
You can try to workaround it with Autofill polyfill:
A polyfill to fire a change event when the browser auto fills form fields

Categories