How to bind an object attribute in TS React - reactjs

To introduce myself to React I am developing a small application - it is a 'media bookmark'. For example you really like a chapter from a certain book, so you give the application the name of the book, chapter, that it is a book, a description and a link if applicable.
The error I keep getting is:
Argument of type 'string' is not assignable to parameter of type 'SetStateAction<MediaBookmarkDTO>'
This is my code:
const [newBookmark, setNewBookmark] = useState<MediaBookmarkDTO>({ bookmarkName: '', bookmarkDescription: '', bookmarkType: '', bookmarkChapOrEp: '', bookmarkLink: '' });
And where I try to bind:
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input type="text" className="form-control" id="BookmarkName" placeholder="Name"
value={newBookmark.bookmarkName} onChange={(e) => setNewBookmark(e.target.value)} />
</div>

Currently you are trying to update newBookmark with a string. Since a string isn't a MediaBookmarkDTO, you get an error. You probably meant to update the name only, which you can do inline like this:
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input
type="text"
className="form-control"
id="BookmarkName"
placeholder="Name"
value={newBookmark.bookmarkName} onChange={(e) => setNewBookmark({
...newBookmark,
bookmarkName: e.target.value,
})}
/>
</div>

First of all welcome to stack overflow family.
newBookmark variable is MediaBookmarkDTO which is a object, when you try to update directly using setNewBookmark(e.target.value), it is trying to provide string value to newBookmark variable, which is where typescript is complaining.
When you are working with forms, in starting I would recommend to have separate state for each field it will help you understand more, when you got the base, then you can use single variable to store all form state. Below is an example to manage the form using a separate state.
import React, { useState } from 'react'
function BookmarkComponent() {
const [bookmarkName, setNewBookmarkName] = useState<string>('');
const [bookmarkDescription, setBookmarkDescription] = useState<string>('');
const [bookmarkType, setBookmarkType] = useState<string>('');
const [bookmarkChapOrEp, setBookmarkChapOrEp] = useState<string>('');
const [bookmarkLink, setBookmarkLink] = useState<string>('');
return (
<form>
<div className="form-group col-md-4">
<label htmlFor="BookmarkName">Name:* </label>
<input type="text" className="form-control" id="BookmarkName" placeholder="Name"
value={bookmarkName} onChange={(e) => setNewBookmarkName(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkDescription">Description:* </label>
<input type="text" className="form-control" id="BookmarkDescription" placeholder="Description"
value={bookmarkDescription} onChange={(e) => setBookmarkDescription(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkType">Type:* </label>
<input type="text" className="form-control" id="BookmarkType" placeholder="Type"
value={bookmarkType} onChange={(e) => setBookmarkType(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookMarkChapter">Chapter:* </label>
<input type="text" className="form-control" id="BookMarkChapter" placeholder="Chapter"
value={bookmarkChapOrEp} onChange={(e) => setBookmarkChapOrEp(e.target.value)} />
</div>
<div className="form-group col-md-4">
<label htmlFor="BookmarkLink">Link:* </label>
<input type="text" className="form-control" id="BookmarkLink" placeholder="Link"
value={bookmarkLink} onChange={(e) => setBookmarkLink(e.target.value)} />
</div>
</form>
)
}
export default BookmarkComponent
When You get more experience you can use Libraries to manage form they are extremely Helpful when managing complex form, below are libraries I used which works very well
React Hook Form
Formik

Related

handleSubmit not working when I try to use the function

I am getting an error in my code of handleSubmit function because ,here I am using this syntax 'export const Register = ()',so what needs to be fixed in my code so that I;m able to use the handleSubmit function I'll paste the code down below it keeps saying its not defined ive tried adding function handleSubmit() and const handleSubmit = () but its still not working any help on how to resolve this error please as i've tried for hours now i'm new to react and wondering as how i would be able to resolve this error
export const Register = () => {
handleSubmit = e => {
e.preventDefault();
console.log('works!');
};
return (
<form onSubmit={this.handleSubmit} >
<h3>Sign Up</h3>
<div className="form-group">
<label> First Name</label>
<input type="text" className="form-control" placeholder="First Name" />
</div>
<div className="form-group">
<label> Last Name</label>
<input type="text" className="form-control" placeholder="Last Name" />
</div>
<div className="form-group">
<label> Email</label>
<input type="email" className="form-control" placeholder="Email" />
</div>
<div className="form-group">
<label> Password</label>
<input type="password" className="form-control" placeholder="Password" />
</div>`
<div className="form-group">
<label> Confirm Password</label>
<input type="password" className="form-control" placeholder="Confirm Password" />
</div>`
<button className="btn btn-primary btn-block" > Sign Up</button>
</form >
);
}
export default Register;
When using the arrow function syntax, the function has to be declared with const. Make it like this:
const handleSubmit = e => {
e.preventDefault();
console.log('works!');
};
Also, you only need to export the Register component once. Using export default Register at the end is sufficient.
And we don't use this in a function component, it is just hadleSubmit:
<form onSubmit={handleSubmit} >
<h3>Sign Up</h3>
<div className="form-group">
<label> First Name</label>
<input type="text" className="form-control" placeholder="First Name" />
</div>
<div className="form-group">
<label> Last Name</label>
<input type="text" className="form-control" placeholder="Last Name" />
</div>
<div className="form-group">
<label> Email</label>
<input type="email" className="form-control" placeholder="Email" />
</div>
<div className="form-group">
<label> Password</label>
<input type="password" className="form-control" placeholder="Password" />
</div>`
<div className="form-group">
<label> Confirm Password</label>
<input type="password" className="form-control" placeholder="Confirm Password" />
</div>`
<button className="btn btn-primary btn-block" > Sign Up</button>
</form >

how to insert data as an array of objects in React JS

my question is a little complicated, I am building a trip-related web application where users can book trips. So I have made a function that increases the number of travelers as the user clicks the + sign. when this function is called it changes the state and another function gets triggered that displays the form to fill in the traveler details. Now this form is rendered according to the number of travelers traveling. how can I set that data in an array of objects?
here's a screenshot guide:
I want the data to be in the state like this:
travelersDetail: [{firstName: 'Farrukh', lastName:'Ayaz', address:'...', city:'Lahore'},
{firstName: 'Dwight', lastName:'Schrute', address:'...', city:'Scranton'},
{firstName: 'Micheal', lastName:'Scott', address:'...', city:'Scranton'},]
My code:
// state
state = {
NumOfTravellers : 1,
travelersDetail: [],
trip: null,
}
// the functions that increases the number of travelers
handleClick = (e) =>{
e.preventDefault();
if(e.target.id == 'plus'){
this.setState({NumOfTravellers: this.state.NumOfTravellers + 1 })
}
else if(e.target.id == 'minus'){
this.state.NumOfTravellers > 1 ? this.setState({NumOfTravellers:
this.state.NumOfTravellers - 1 }) : alert("can't be less than that :)")
}
}
// the function that returns the traveler details form, according to the number of travelers traveling.
const numberOfTravelers = () =>{
var travellers = [];
for(let t = 0; t < this.state.NumOfTravellers; t++){
travellers.push(
<div >
<h4> Traveller # {t+1} Details</h4><br/>
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="firstName">First Name</label>
<input type="firstName" className="form-control" onChange={this.handleTDChange} id="firstName" placeholder="FirstName" />
</div>
<div className="form-group col-md-6">
<label htmlFor="lastName">Last Name</label>
<input type="lastName" className="form-control" onChange={this.handleTDChange} id="lastName" placeholder="LastName" />
</div>
</div>
<div className="form-group">
<label htmlFor="address">Address</label>
<input type="text" className="form-control" onChange={this.handleTDChange} id="address" placeholder="1234 Main St" />
</div>
<div className="form-group">
<label htmlFor="phoneNumber">Phone Number</label>
<input type="tel" className="form-control" onChange={this.handleTDChange} id="phoneNumber" placeholder="+92..." />
</div>
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="city">City</label>
<select onChange={this.handleTDChange} id="city" className="form-control">
<option selected>Choose...</option>
<option>Lahore</option>
<option>Islamabad</option>
<option>Karachi</option>
<option>Rawalpindi</option>
<option>Quetta</option>
<option>Multan</option>
</select>
</div>
<div className="form-group col-md-4">
<label htmlFor="state">State</label>
<select onChange={this.handleTDChange} id="state" className="form-control">
<option selected>Choose...</option>
<option>Pakistan</option>
</select>
</div>
<div className="form-group col-md-2">
<label htmlFor="zip">Zip</label>
<input type="text" className="form-control" onChange={this.handleTDChange} id="zip" />
</div>
</div>
</div>);
}
return travellers
}
I don't completely understand you problem, what I understand is.
There is a controller, Plus and Minus. On click of Plus a new Traveler form has to be added and on click of minus the last Travelers form will be removed. And also the traveler counter is incremented or decremented based on the button click
You would not want 2 variables, 1 to keep track of the number of travelers and other to store the traveler details, you can maintain only 1 variable. Just have traverlerDetails, we can get the number of travelers form the size of the traverlerDeterails array.
// state values
this.state = {
travelersDetail: [],
trip: null,
};
handleClick = (clickEvent) => {
clickEvent.preventDefault();
const travelersDetailCopy = [...this.state.travelersDetail];
if (e.target.id == 'plus') {
travelersDetailCopy.push({
firstName: '', lastName: '', address: '', city: '' // Add empty data
});
} else if (e.target.id == 'minus') {
if (this.state.travelersDetail.length === 1) {
alert("Can't be less than 1");
} else {
travelersDetailCopy.pop();
}
}
this.setState({
travelersDetail: travelersDetailCopy
});
}
const numberOfTraverlers = () => {
return this.state.travelersDetail.map((travelerDetails, index) => {
return (
<div key={index}>
<h4> Traveller # {index + 1} Details</h4><br />
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="firstName">First Name</label>
<input type="firstName" className="form-control" onChange={(event) => {this.handleTDChange(event, index, "firstName")}} id="firstName" placeholder="FirstName" />
</div>
<div className="form-group col-md-6">
<label htmlFor="lastName">Last Name</label>
<input type="lastName" className="form-control" onChange={(event) => {this.handleTDChange(event, index, "lastName")}} id="lastName" placeholder="LastName" />
</div>
</div>
<div className="form-group">
<label htmlFor="address">Address</label>
<input type="text" className="form-control" onChange={(event) => {this.handleTDChange(event, index, "address")}} id="address" placeholder="1234 Main St" />
</div>
<div className="form-group">
<label htmlFor="phoneNumber">Phone Number</label>
<input type="tel" className="form-control" onChange={(event) => {this.handleTDChange(event, index, "phoneNumber")}} id="phoneNumber" placeholder="+92..." />
</div>
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="city">City</label>
<select onChange={(event) => {this.handleTDChange(event, index, "city")}} id="city" className="form-control">
<option selected>Choose...</option>
<option>Lahore</option>
<option>Islamabad</option>
<option>Karachi</option>
<option>Rawalpindi</option>
<option>Quetta</option>
<option>Multan</option>
</select>
</div>
<div className="form-group col-md-4">
<label htmlFor="state">State</label>
<select onChange={(event) => {this.handleTDChange(event, index, "state")}} id="state" className="form-control">
<option selected>Choose...</option>
<option>Pakistan</option>
</select>
</div>
<div className="form-group col-md-2">
<label htmlFor="zip">Zip</label>
<input type="text" className="form-control" onChange={(event) => {this.handleTDChange(event, index, "zip")}} id="zip" />
</div>
</div>
</div>
)
})
}
handleTDChange(event, index, updateField) {
const arrayCopy = [...this.state.travelersDetail];
arrayCopy[index][updateField] = event.target.value;
this.setState({travelersDetail: arrayCopy});
}
Use this.state.travelersDetail.length to display the number of travelers.
Don't use for-loop, make use of built in functions like forEach, map, filter and other methods.
Update :
To handle onChange events, you can have multiple handleChange event handler.
But if you want to do it in a single, you can pass few additional argument. First being the actual event, second the index of the travelerDetails object, third being the property that needs to be updated.
There is a much better way of doing this, extract the content in side the map and create a separate component. Which would contain the logic related to the component. With this updation and also maintenance of the code is much easier
You should be using the array.push() method detailed in javascript to add an element to an existing array.
Example
const array = [];
array.push({ id: 'someId', name: 'someName' });
See documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

React JS - Proper way to show the values from json array

I have a requirement to fetch and show the values in the form text box. I need to check whether if the value exists and not equal to null then show the values in the text box otherwise show a blank field.
The existing code implementation shows something like this :
{
this.state.testJson.Names ? this.state.testJson.Names.length > 0 ? this.state.testJson.Names.map(response =>
<div className="form-group col-md-3" key={response.nameId}>
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value={response.otherName} type="text" className="form-control" id="firstName" />
</div>
):
<div className="form-group col-md-3">
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value='' type="text" className="form-control" id="firstName" />
</div> :
<div className="form-group col-md-3">
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input name="firstName" value='' type="text" className="form-control" id="firstName" />
</div>
}
I somehow feel this is not the best way to implement it as I need to avoid code repetition. Could someone tell me what is the better way to achieve this?
Thanks
There's a nice sintactic sugar for modern JavaScript and React (if you are using React, you must likely have it), you simply add a question mark before the object you're not sure it exists like:
this.state?.testJson?.Names?.length > 0
Also, you could have default values of a nullish variable like:
// names will be an empty array if it doesn't exist or if it's nullish
const names = this.state?.testJson?.Names ?? [];
All together is:
const names = this.state?.testJson?.Names ?? [];
return(
names.map(response =>
<div className="form-group col-md-3" key={response?.nameId}>
<label htmlFor="firstName">{Liferay.Language.get('first-name')}</label>
<input
name="firstName"
value={response?.otherName}
type="text"
className="form-control"
id="firstName"
/>
</div>
) : ....rest of the code!
);
You can fetching inside some cards
<div className="row">
{!names
? "Loading..."
: names.map((name) => {
return (
<div className="col">
<div className="card-body">
<h5 className="card-title">{name.firstname}</h5>
</div>
</div>
</div>
);
})}
</div>

Setting a state in react.js with two-way binding

i'am working in a project using react.js and fire-base, i have a form when i set the input with my state that is fill with data of fire-base, and is working i can update and create new registry, but i think that my onChangeHandle() for the inputs is not the correct way to do it.
This is my form:
render(){
return (
<div className="row">
<div className="col-xs-3 col-sm-3 col-md-3 col-lg-3"></div>
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6">
<div className="form-group">
<label >Nombre de Proyecto</label>
<input type='text' value={this.state.proyectName} onChange={(event)=>this.onChangeHandle('p',event)}className="form-control" id="project_name"/>
</div>
<div className="form-group">
<label >Inspiracion</label>
<textarea value={this.state.inspiration} onChange={(event)=>this.onChangeHandle('i',event)} rows="4" cols="50" className="form-control" id="inspiration"/>
</div>
<div className="form-group">
<label >Que problema resuelve</label>
<textarea value={this.state.whatDoes} onChange={(event)=>this.onChangeHandle('w',event)} rows="4" cols="50" className="form-control" id="what_does"/>
</div>
<div className="form-group">
<label >Como esta hecho</label>
<textarea value={this.state.howBuild} onChange={(event)=>this.onChangeHandle('h',event)} rows="4" cols="50" className="form-control" id="how_build"/>
</div>
<div className="form-group">
<label >Integrantes</label>
<input type='text' className="form-control" id="team"/>
</div>
<div className="form-group">
<button className="form-control btn btn-primary" onClick={()=>this.writeStartupData()} >Agregar </button>
</div>
</div>
</div>
)
}
And here is my event handler:
onChangeHandle(exp,event){
switch(exp){
case "p":
this.setState({
proyectName: event.target.value,
});
break;
case "i":
this.setState({
inspiration: event.target.value,
});
break;
case "w":
this.setState({
whatDoes: event.target.value,
});
break;
case "h":
this.setState({
howBuild: event.target.value,
});
break;
case "t":
this.setState({
team: event.target.value,
});
break;
}
}
I think you should do something like this.
<div className="form-group">
<label >something</label>
<input
type='text'
value={this.state.something}
onChange={event => this.setState({something: event.target.value})}
className="form-control" id="project_name"/>
</div>
The code for your event handlers is not something I'd consider very readable, DRY, or adherent to any react best practises.
Using an anonymous arrow function and calling setState from there, like #vitaliy-andrianov has done, is perfectly fine. There is just one downside with arrow functions in that case: the functions are re-created on every re-render, incurring a small (most likely negligible) performance penalty. It could also be a little more dry. Below is another acceptable alternative:
// Component class method
handleFormChange(event) {
this.setState({
[event.currentTarget.name]: event.currentTarget.value
});
}
// ... and if you like destructuring
handleFormChange({currentTarget: {name, value}}) {
this.setState({ [name]: value });
}
// An example input
<input
className="form-control"
name="proyectName"
type="text"
value={this.state.proyectName}
onChange={this.handleFormChange} />
Note: I am passing handleFormChange directly to the onChange prop; for this to work, the function has to be bound to the scope of the class. So make sure you do that in the constructor (this.handleFormChange.bind(this)), or just use an arrow function.

Handling application data as a frontend developer

So I'm working on building my first application in React. I really don't know much about databases, data models, schema, etc. Is it bad practice to just store my data in the browser as I develop then worry about hooking my app up to something like firebase later?
For example, below I store some user data in an object called user and just go from there. As long as I have some way of accessing it later. Keeping in mind I'm in the beginning stages of frontend development is this bad practice?
Thanks!
var SignUpForm = React.createClass({
getUserInfo : function(event) {
event.preventDefault();
var user = {
name : this.refs.name.value,
userName : this.refs.userName.value,
email : this.refs.email.value,
password : this.refs.password.value
}
console.log(user)
this.refs.signUpForm.reset();
},
render : function() {
return (
<form onSubmit={this.getUserInfo} ref="signUpForm">
<div className="form-group">
<label>First and Last Name</label>
<input type="text" className="form-control" ref="name" />
</div>
<div className="form-group">
<label>Username</label>
<input type="text" className="form-control" ref="userName" />
</div>
<div className="form-group">
<label>Email Address</label>
<input type="email" className="form-control" ref="email" />
</div>
<div className="form-group">
<label>Password</label>
<input type="password" className="form-control" ref="password" />
</div>
<button type="submit" className="btn btn-primary">Sign Up</button>
</form>
);
}
});

Resources