I have the following event in onSubmit using React and I am changing it from Javascript to TypeScript.
const submitUserHandler = (e) => {
e.preventDefault();
dispatch(
authenticate({
name: e.target.personname.value,
pw: e.target.password.value,
})
);
};
I have tried assigning 'e: React.ChangeEvent' to the event, but it prompts an error like this:
Property 'personname' does not exist on type 'EventTarget & HTMLInputElement'.
How could I specify the type also including personname and password?
Thanks!
You can do something like this
<form
ref={formRef}
onSubmit={(e: React.SyntheticEvent) => {
e.preventDefault();
const target = e.target as typeof e.target & {
personname: { value: string };
password: { value: string };
};
const email = target.personname.value; // typechecks!
const password = target.password.value; // typechecks!
// etc...
}}
>
<div>
<label>
Email:
<input type="personname" name="personname" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Log in" />
</div>
</form>
For details, reference
I am sending my state as a stringified variable from a form to a POST request through a lamda server which then parses it and sends it to sendgrid which there I am using send grids templating feature. Which requires me to have json Formatted like this in order to loop over one particular part (multiple checkboxes) which all should have the same key but a different value, set by a reason="Weight Loss" in the form. Here is how the eventual json needs to be formed.
{
"name" :"anders",
"message" : "winfsdafasfdsfsadfsadnipeg",
"package" : "silver",
"email" : "email#email.com",
"subject" : "fdsafas",
"data":{
"reasonArray":[
{
"reason":"weightLoss"
},
{
"reason":"Sport"
}
]
}
}
Then I can do some magic and loop over the reason's that were checked in the checkbox
<ol>
{{#each data.reasonArray}}
<li>{{this.reason}} </li>
{{/each}}
</ol>
Now I had it working if I left the state with single key value pairs and don't have the data portion.
Here is what my initial state looked like working.
const [formState, setFormState] = React.useState({
name: "",
package: `${data.datoCmsPricing.title}`,
email: "",
subject: "",
weightLoss:"",
strength:"",
sport:"",
message: "",
})
I then had the following onChange event that set the state with the name of the input field as the key and the value or checked state as the value. Seen here
const onChange = (e) => {
if (e.target.type === 'checkbox' && !e.target.checked) {
setFormState({...formState, [e.target.name]: e.target.checked});
} else {
setFormState({...formState, [e.target.name]: e.target.value });
}
}
and here is my form
<form onSubmit={submitForm}>
{/* <input type="text" name="package" value={data.datoCmsPricing.title} /> */}
<label>
Name
<input
type="text"
name="name"
value={formState.name}
onChange={onChange}
/>
</label>
<label>
Email
<input
type="email"
name="email"
value={formState.email}
onChange={onChange}
/>
</label>
<label>
Subject
<input
type="text"
name="subject"
value={formState.subject}
onChange={onChange}
/>
</label>
<div>
<h3>Reasons for wanting to train</h3>
<label>
Weight Loss
<input
type="checkbox"
name="weightLoss"
checked={formState.weightLoss}
onChange={onChange}
/>
</label>
<label>
Strength
<input
type="checkbox"
name="strength"
checked={formState.strength}
onChange={onChange}
/>
</label>
<label>
Sport
<input
type="checkbox"
name="sport"
checked={formState.sport}
onChange={onChange}
/>
</label>
</div>
<label>
message
<textarea
name="message"
value={formState.message}
onChange={onChange}
/>
</label>
<button type="submit">Submit</button>
</form>
I then send it off to my lamdba function
const response = await fetch("/.netlify/functions/sendmail", {
method: "POST",
body: JSON.stringify(formState),
})
Now my state looks like the following in json after being sent to lamdbda function and being parsed
{
name: 'Anders',
package: 'silver',
email: 'email#email.com',
subject: 'fdsafa',
weightLoss: 'on',
strength: 'on',
sport: 'on',
message: 'fdsafasf'
}
Now I want to have my initial state to look like the format that sendgird wants it in, so this is what I attempted with my state setup.
const [formState, setFormState] = React.useState({
name: "",
package: `${data.datoCmsPricing.title}`,
email: "",
subject: "",
weightLoss:"",
strength:"",
sport:"",
message: "",
data:{
reasonArray:[
{
reason:""
},
{
reason:""
}
]
}
})
Then I tried to update the onChange event for the checked values with the following, I also update my form so it grabs a user friendly name for the reason. Seen below this code
const onChange = (e) => {
if (e.target.type === 'checkbox' && !e.target.checked) {
setFormState({...formState, data:{ reasonArray:[ { reason:e.target.reason}, ]}});
}
...
}
Form Changes
...
<label>
Weight Loss
<input
type="checkbox"
name="weightLoss"
reason="weightLoss"
checked={formState.weightLoss}
onChange={onChange}
/>
</label>
<label>
Strength
<input
type="checkbox"
name="strength"
reason="strength"
checked={formState.strength}
onChange={onChange}
/>
</label>
<label>
Sport
<input
type="checkbox"
name="sport"
reason="sport"
checked={formState.sport}
onChange={onChange}
/>
</label>
...
The resulting Json I get after the Post request is this, with my attempt. It does not update the data part. So the resulting Json is in the right format, but it doesn't have the reason's attached. Thanks ahead of time for any help.
{
"name":"Anders",
"package":"Silver",
"email":"email#email.com",
"subject":"fdsaf",
"weightLoss":"on",
"strength":"on",
"sport":"on",
"message":"fdsafas",
"data":{
"reasonArray":[
{
"reason":""
},
{
"reason":""
}
]
}
}
Attempting Rabi's answer
...
const prepareDataForApi = (formData) => {
const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep
newFormData.data = {
reasonArray:[]
};
Object.keys(newFormData.reasons).forEach(key => {
if(newFormData.reasons[key]){
newFormData.data.reasonArray.push({reason: key})
}
});
delete newFormData.reasons;
return newFormData;
}
const submitForm = async (e) => {
e.preventDefault();
setForm(false);
// const newFormData = prepareDataForApi(formData);
const newFormData = prepareDataForApi(formState);
console.log(newFormData);
...
1.Keep your initial state like this :
{
"name":"Anders",
"package":"Silver",
"email":"email#email.com",
"subject":"fdsaf",
"message":"fdsafas",
"reasons": {
"weightLoss": true,
"strength": true,
"sport": true,
}
}
Modify onChange():
const onChange = (e) => {
if (e.target.type === 'checkbox') {
const changedReason = e.target.getAttribute('name');
setFormState({...formState, reasons:{...formState.reasons, [changedReason]: !formState.reasons[changedReason]}});
}
...
}
Change form's onSubmit():
Before calling api , call converter function which will convert formState to JSON format required by your lambda function
const prepareDataForApi = (formData) => {
const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep
newFormData.data = {
reasonArray:[]
};
Object.keys(newFormData.reasons).forEach(key => {
if(newFormData.reasons[key]){
newFormData.data.reasonArray.push({reason: key})
}
});
delete newFormData.reasons;
return newFormData;
}
It looks like your new onChange is not replacing all of your nested values when you update a key. Try this instead:
setFormState({
...formState,
data:{
...formState.data, // keep keys from previous data object (not necessary if it only contains the key you are specifying though)
reasonArray:[
...formState.data.reasonArray, // keep previous entries from reasonArray
{ reason:e.target.reason},
]
}
});
An alternative would be to use an effect.
const [formState, setFormState] = React.useState({...}):
// Runs every time weightLoss is changed
React.useEffect(() => {
let newReasonArray
if (formState.weightLoss) {
newReasonArray = [...formState.reasonArray]; // So we dont mutate state
newReasonArray.push({reason: 'weightLoss'});
} else {
// If you want to remove it from the array if its unchecked
newReasonArray = [...formState.reasonArray];
newReasonArray.filter((reason) => (reason.reason != 'weightLoss'));
}
console.log(newReasonArray) // Test if it is updated correctly
// Set the state with the new array
setFormState({...formState, data: { reasonArray: newReasonArray }});
}, [formState.weightLoss]);
I am new to React and this thing is confusing me a lot. I have root component that has an array and I am passing functions ADD and DELETE as props to the child component ui_forms. In the child component, I am taking input parameters and pushing or deleting from array depending on the button pressed. However, I cannot seem to perform push/delete operation more than once because I guess I am injecting the child component only once in the root component.
Is there by any chance a way to perform push or delete as many times a user wants by sending props only once?
Thank you
App.js
import FormsMap from './form_map';
class App extends Component {
state = {
maps : [
{'regno' : 'Karan', 'id' : 1},
{regno : 'Sahil', 'id' : 2},
{'regno' : 'Rahul', id : 4},
{regno : 'Mohit', id : 5},
{regno : 'Kartik', id : 3}
]
};
function_as_props(list1) {
console.log(this.state.maps);
let ninja = [...this.state.maps];
console.log(ninja);
ninja.push({"regno" : list1, "id" : Math.random()*10});
this.setState({
maps : ninja
});
console.log(ninja);
function_as_props1(name) {
let t = this.state.maps.indexOf(name);
let x = this.state.maps.splice(t,1);
console.log(x);
this.setState({
maps : x
});
console.log(this.state.maps);
}
}
render() {
const p = this.state.maps.map(list => {
return(
<div key={list.id}> {list.regno} </div>
);
})
return(
<FormsMap transpose = {this.function_as_props.bind(this)} traverse ={this.function_as_props1.bind(this)} /> <br />
);
}
}
export default app;
form_map.js
import React, { Component } from 'react';
class FormsMap extends Component {
state = {
name : null,
age : null,
hobby : null
};
changes(e) {
this.setState({
[e.target.id] : e.target.value
});
}
handle = (e) => {
e.preventDefault();
console.log(this.state);
this.props.transpose(this.state.name);
}
dels = (e) => {
this.setState({
[e.target.id] : e.target.value
});
}
del_button(e) {
e.preventDefault();
//console.log(this.state.name);
this.props.traverse(this.state.name);
}
render() {
return(
<React.Fragment>
<form onSubmit={this.handle}> {/* After entering all info, Press Enter*/}
<label htmlFor="labels"> Name : </label>
<input type="text" id="name" placeholder="Your name goes here..." onChange={this.changes.bind(this)} />
<label htmlFor="labels"> Age : </label>
<input type="text" id="age" placeholder="Your age goes here..." onChange={this.changes.bind(this)} />
<label htmlFor="labels"> Hobby : </label>
<input type="text" id="hobby" placeholder="Your hobby goes here..." onChange={this.changes.bind(this)} /> <br /><br />
<input type="submit" value="SUBMIT" /><br /><br />
</form>
<input type="text" id="name" placeholder="Enter name to delete..." onChange={this.dels} /> <button onClick={this.del_button.bind(this)}> DELETE </button>
</React.Fragment>
);
}
}
export default FormsMap;
Try this
App.js
import React, { Component } from "react";
import FormsMap from "./components/FormsMap";
class App extends Component {
constructor(props) {
super(props);
this.state = {
maps: [
{ regno: "Karan", id: 1 },
{ regno: "Sahil", id: 2 },
{ regno: "Rahul", id: 4 },
{ regno: "Mohit", id: 5 },
{ regno: "Kartik", id: 3 }
]
};
}
function_as_props(list1) {
let ninja = this.state.maps.concat({
regno: list1,
id: Math.random() * 10
});
this.setState({ maps: ninja });
}
function_as_props1(name) {
let x = this.state.maps.filter(
list => list.regno.toLocaleLowerCase() !== name.toLocaleLowerCase()
);
this.setState({
maps: x
});
}
render() {
return (
<React.Fragment>
{this.state.maps.map(list => <div key={list.id}>{list.regno}</div>)}
<FormsMap
transpose={this.function_as_props.bind(this)}
traverse={this.function_as_props1.bind(this)}
/>
</React.Fragment>
);
}
}
export default App;
FormsMap.js
import React, { Component } from "react";
class FormsMap extends Component {
state = {
name: null,
age: null,
hobby: null
};
changes(e) {
this.setState({
[e.target.id]: e.target.value
});
}
handle = e => {
e.preventDefault();
this.props.transpose(this.state.name);
};
dels = e => {
this.setState({
[e.target.id]: e.target.value
});
};
del_button(e) {
e.preventDefault();
this.props.traverse(this.state.name);
}
render() {
return (
<React.Fragment>
<form onSubmit={this.handle}>
{" "}
{/* After entering all info, Press Enter*/}
<label htmlFor="labels"> Name : </label>
<input
type="text"
id="name"
placeholder="Your name goes here..."
onChange={this.changes.bind(this)}
/>
<label htmlFor="labels"> Age : </label>
<input
type="text"
id="age"
placeholder="Your age goes here..."
onChange={this.changes.bind(this)}
/>
<label htmlFor="labels"> Hobby : </label>
<input
type="text"
id="hobby"
placeholder="Your hobby goes here..."
onChange={this.changes.bind(this)}
/>{" "}
<br />
<br />
<input type="submit" value="SUBMIT" />
<br />
<br />
</form>
<input
type="text"
id="name"
placeholder="Enter name to delete..."
onChange={this.dels}
/>{" "}
<button onClick={this.del_button.bind(this)}> DELETE </button>
</React.Fragment>
);
}
}
export default FormsMap;
This is the demo: https://codesandbox.io/s/xl97xm6zpo
I am trying to add data to my collection inside an object from my TypeScript code. I am successfully getting the name and type from the view in the html binding So I was wondering how I can edit the this.newList.socialData model via code before adding the new list to my database.
Html:
<mat-form-field>
<input matInput placeholder="List Name" [(ngModel)]="newList.name" name="name" type="text" class="form-control rounded-0" required>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="List Type" [(ngModel)]="newList.type" name="type" type="text" class="form-control rounded-0" required>
</mat-form-field>
<button (click)="addList()" type="button" class="btn btn-primary float-right">Create New</button>
Declaration:
newList: List = {} as List
TypeScript:
addList() {
let tt = {} as SocialData;
//tt.socialId = 'jj'
//this.newList = {} as List;
// I would like to add test data to socialData here
this.newList.socialData.push(tt);
this.listService.addList(this.newList)
.subscribe(
res => {
this.fetchLists();
},
err => console.log(err)
)
}
Model:
export class List {
name: String;
type: String;
inputValue: String;
socialData: [SocialData]
}
export class SocialData {
socialId: String
}
I guess to just want to add a new item to the socialData array.
You need to make 2 changes in your code:
1. Declaration
export class List {
name: String;
type: String;
inputValue: String;
// socialData: [SocialData]; // This is wrong
socialData: SocialData[]; // declares a type of array
constructor() {
this.socialData = []; // Initialize the array to empty
}
}
2. Creating the instance:
// let tt = {} as SocialData; // This will just cast an empty object into List
newList: List = new List(); // Will create an instance of List
Just make these changes and the code should work.
Here's my class:
interface IHobby {
name: string;
type: string;
}
class User {
constructor(public name: string, public hobbies: IHobby[]) { }
}
Now I'm trying to do a template form in Angular 4. I've noticed that in order to display the hobbies, I need to iterate through them. So, here's my HTML:
<div *ngFor="let h of user.hobbies; let i = index">
#{{i}}<br />
<input [(ngModel)]="h.name" name="hobby_name[{{i}}]" /> <br /><br />
<input [(ngModel)]="h.type" name="type[{{i}}]" />
<br /><br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
While this works, I'm not quite sure if:
I'm doing this the correct Angular 4 way - should I use ngModel for each item in the array?
Is it OK to define names as name="hobby_name[{{i}}]"? Because name="hobby_name_{{i}}" works as well. What is the correct, HTML5 and Angular 4 way?
Here's a working sample: https://plnkr.co/edit/chOsfhBThD3dA9FFqNPX?p=preview
You don't really need to use name attribute since you're binding the inputs directly to the model. If you want to see instant changes in your model, you can keep using [(ngModel)], but if you want to update your model on form submit only, then you should not use ngModel. I would do a reactive form like this:
<div [formGroup]="form">
<div formArrayName="hobbiesFormArray">
<div *ngFor="let h of user.hobbies; let i = index" [formGroupName]="i">
#{{i}}<br />
<input formControlName="name" /> <br /><br />
<input formControlName="type" />
<br /><br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
</div>
</div>
And then in your component I would do something like:
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = this.fb.group({
name: '',
hobbies: this.fb.array([])
});
}
ngOnChanges() {
this.form.reset({
name: this.user.name
});
this.setHobbies(this.user.hobbies);
}
get hobbiesFormArray(): FormArray {
return this.form.get('hobbiesFormArray') as FormArray;
};
setHobbies(hobbies: IHobby[]) {
const hobbiesFormArray = this.fb.array(hobbies.map(hobby => this.fb.group(hobby)));
this.form.setControl('hobbiesFormArray', this.fb.array());
}
Then in your onSubmit method I would convert the form model back to the user object:
const formModel = this.form.value;
const newHobbies: IHobby[] = formModel.hobbiesFormArray
.map(() => (hobby: IHobby) => Object.assign({}, hobby))
const newUser: User = {
name: formModel.name as string,
hobbies: newHobbies
};
And save newUser object with whatever means you have.