Forms with React components - reactjs

I want to create a form with React. I came up with the following:
export class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
errors: []
};
}
render() {
return (
<form className="Welcome">
<div className="hello">{ this.props.sayHello() } I'm <Link to="/life">Marco</Link>! Welcome to my hood.</div>
<div className="question-box">
<div className="question"><span className="underline" >How much time do you have?</span></div>
<input className="minutes" type="text" value={this.props.minutes}
onChange={ (e) => this.props.updatePreferences("minutes", e.target.value) }/> minutes.
</div>
<div className="question-box">
<div className="question"><span className="underline">What do you fancy?</span></div>
<div className="answer">
<span className="choice">
<input type="checkbox" id="business" defaultChecked={ this.props.interests.business }/>
<label htmlFor="business">Business</label>
</span>
<span className="choice">
<input type="checkbox" id="code" defaultChecked={ this.props.interests.code }/>
<label htmlFor="code">Code</label>
</span>
<span className="choice">
<input type="checkbox" id="design" defaultChecked={ this.props.interests.design } />
<label htmlFor="design">Design</label>
</span>
</div>{/* end of .answer*/}
</div>{/* end .question-box*/}
<button>Show me magic</button>
<div className="errors">
No error. All good.
</div>
</form>
);
}
}
The onChange functions are in the parent component which also holds the state. But every time they are called the whole component is reloaded. Should the whole form be on a separate component or I should create separate components for each input?

React call render function in response of state change. So if component does not receive state change event it most probably will not rerendered but not necessarily. It may rernder anyway. ref

Related

How do I clear the textarea's value when clicked on the addbtn in reactjs?

I used the following code, but the text does not clear when I click the addbtn.
This is a reactjs project.
function Addpage() {
const[note, setnote] = useState("");
function textchange(e) {
setnote(e.target.value);
console.log(note);
};
function savenote(){
localStorage.setItem('savednotes', note);
setnote("");
};
return (
<>
<Headersmall/>
<br/>
<div className="addcard">
<h1>Add new note.</h1>
<div className="title">
<input type="text" className="titlebox" id="addtitle" placeholder="Title"/>
</div>
<br/>
<div className="note">
<textarea type="text" className="notebox" id="addtxt" placeholder="Note" onChange = {textchange}/>
</div>
<br/>
<button className="addbtn" id='addbtn' onClick = {savenote}>Save</button>
</div>
<br/>
</>
)
}
When you're setting the value of note as an empty string by doing setnote("") inside savenote() function, you ARE changing the value of the state variable note, but it doesn't reflect in the textarea because your textarea input doesn't have a value associated to it. Try adding value={note} which will mean that there will be a "set and fetch" relationship between the textarea and the 'note' state variable
<textarea
value={note}
type="text"
className="notebox"
id="addtxt"
placeholder="Note"
onChange={textchange}
>

onChange using Formik Validation

I'm currently having getting my input values to appear on screen as im typing. For example, I have a form that requires a first name and last name. Upon typing in those values I am trying to display the inputs typed onto the DOM. This was successful earlier with an onChange, and using this.state.firstName, this.state.lastName. I have implemented formik validation and currently my inputs only appear after I upload an image which has a set state.
I have an onChange passing its values
handleChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
<---->
<Formik
initialValues={this.state.formData}
enableReinitialize={true}
validationSchema={userProfileValidation}
onSubmit={this.handleSubmit}
render={formikProps => (
<div className="container-fluid">
<div className="edit-profile">
<div className="row">
<div className="col-lg-4">
<div className="card">
<div className="card-header">
<h3 className="card-title">My Profile</h3>
</div>
<div className="card-body">
<div className="row mb-2">
<div className="col-auto">
<img
className="img-90 rounded-circle"
alt=""
src={
this.state.formData.avatarUrl
? this.state.formData.avatarUrl
: "https://iupac.org/wp-
content/uploads/2018/05/default-avatar.png"
}
/>
</div>
<div className="col">
<h4 className="mb-1">
{this.state.formData.firstName} {""}{" "}
{this.state.formData.mi} {""}{" "}
{this.state.formData.lastName}{" "}
</h4>
</div>
</div>
</div>
</div>
I am able to show what input on a setState but live like it previously shown.
You're trying to mix two separate things. Formik exist so that you don't have to manage your component level form state by yourself doing so is hard and formik does that for you.
You should pass an object containing form initial field values to initialValues prop instead of this.state.formData
To update a DOM value based on a field input somewhere, you can do this
<Field
name="email"
type="email"
onChange={e => {
// call the built-in handleChange for formik
handleChange(e)
// and do something about e
let someValue = e.currentTarget.value
this.updateEmailField(someValue) // Update a DOM element on this function
...
}}
/>

React I can't change my input field's value if it has a value and onChange

I have a CRUD application, in my component's state I save the name of the user. Now the problem is: I have an input field for name in my form and when the user clicks on update user from the list, the selected user's name will appear on the input field and then I'm unable to change the name because the input field won't let me. I delete a letter and it reappears automatically, how can I fix this?
In my component's render method:
<div className="col-sm-9">
<div className="form-check">
<input name="user_name" ref="user_name" className="form-check-input" type="checkbox" value="1" id="user_name"
value={this.state.user_name} onChange={this.onChange} />
</div>
</div>
Outside my render method:
onChange(e) {
this.setState({[e.target.name]: e.target.value})
}
Have you bind your onChange method(i suppose that is the issue, that your state is not getting updated). If not, then :
try changing to this :
onChange = (e) => {
this.setState({[e.target.name]: e.target.value})
}
Or to this :
<div className="col-sm-9">
<div className="form-check">
<input name="user_name" ref="user_name" className="form-check-input" type="checkbox" value="1" id="user_name"
value={this.state.user_name} onChange={this.onChange.bind(this)} />
</div>
</div>

React 'fetch' response successful, but setState not working

I'm new to React, working through a ".Net core CRUD with React" tutorial and trying to tweak it along the way to suit my own needs.
The page I'm dealing with here is an Add/Edit entry page. It works fine for rendering a default form with default values but doesn't render anything if the values are collected from a fetch call.
The important details are below:
interface AddPortfolioProjectDataState {
title: string;
projectData: PortfolioProject;
loading: boolean;
}
The page is told to render as follows:
public render() {
let contents = this.state.loading
? <p><em>Loading Project...</em></p>
: this.renderCreateForm(this.state.projectData.type, this.state.projectData.tech);
return (
<div>
<h1>{this.state.title}</h1>
<h3>Project</h3>
<hr />
{contents}
</div>
)
}
If I want to add a new item, therefore using a default PortfolioProject object with default values, it works fine. However, if I want to edit an old entry, I have to grab it from the server, like so:
fetch('api/StuartAitkenWebsite/GetPortfolioProject/' + projID)
.then(response => response.json() as Promise<PortfolioProject>)
.then(data => {
this.setState({ title: "Edit", loading: false, projectData: data });
});
In the debug console on Firefox, I can see the whole server process runs smoothly:
GET http://localhost:62669/api/StuartAitkenWebsite/GetPortfolioProject/2
Response payload: {"id":2,"name":"Particles Sim","projectDate":"2017-01-01T00:00:00","projectDurationWeeks":1,"type":"Desktop App","tech":"C++, SFML","views":0,"creationDate":"2018-10-22T00:00:00","modifiedDate":"2018-10-22T00:00:00","status":1}`
It gives a JSON output of the payload too, which I can't easily copy-paste here so I'll give a screenshot:
There are no server error responses, no React errors, nothing.
But that's as far as it gets.
The page remains showing 'loading', even though the data is there and ready and wants to be displayed.
From this, I can gather that the final step of the fetch call is not succeeding, because this.setState({ title: "Edit", loading: false, projectData: data }); is clearly not having any effect on the page data.
I have other fetch calls which look exactly the same but work fine. I can't see what I'm missing here.
The one and the only difference I notice is this:
When I use this component to create a fresh 'Add Project' form, the state is set like so:
this.state = {
title: "Create",
loading: false,
projectData: new PortfolioProject,
};
But when I do it from the API, it's set like so:
this.setState({
title: "Edit",
loading: false,
projectData: data
});
The successful version uses this.state, and the unsuccessful version uses this.setState
I don't know what this can mean though. As I said, no errors are being thrown, I'm sticking to the tutorial format, and it works fine in other parts of the project.
Thanks.
UPDATE
I've put a log in at the point where renderCreateForm() is called. It seems setState is actually working. Therefore, the problem must be in renderCreateForm() so I'll post that code below. Sorry it's sort of large.
private renderCreateForm(projectTypes: string, projectTech: string) {
console.log(this.state.loading); // "false"
console.log(this.state.projectData); //"Object { id:1, name: "Muon Detector".. etc
//so the render is getting the data
return (
<form onSubmit={this.handleSave}>
<div className="form-group row" >
<input type="hidden" name="Id" value={this.state.projectData.id} />
</div>
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Name">Name</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Name" defaultValue={this.state.projectData.name} required />
</div>
</div>
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="ProjectDate">Project Date</label>
<div className="col-md-4">
<input className="form-control" type="date" name="ProjectDate" defaultValue={this.state.projectData.creationDate.toDateString()} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="ProjectDurationWeeks">Project Duration (weeks)</label>
<div className="col-md-4">
<input className="form-control" type="text" name="ProjectDurationWeeks" defaultValue={this.state.projectData.projectDurationWeeks.toString()} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Type">Project Type</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Type" defaultValue={this.state.projectData.type} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Tech">Project Tech</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Tech" defaultValue={this.state.projectData.tech} required />
</div>
</div >
<div className="form-group row" >
<input type="hidden" name="Views" value={this.state.projectData.views} />
</div>
<div className="form-group row" >
<input type="hidden" name="CreationDate" value={this.state.projectData.creationDate.toDateString()} />
</div>
<div className="form-group row" >
<input type="hidden" name="ModifiedDate" value={this.state.projectData.modifiedDate.toDateString()} />
</div>
<div className="form-group row" >
<input type="hidden" name="Status" value={this.state.projectData.status} />
</div>
<div className="form-group">
<button type="submit" className="btn btn-default">Save</button>
<button className="btn" onClick={this.handleCancel}>Cancel</button>
</div >
</form>
)
}
UPDATE 2: Added some screenshots showing how things appear so far.
How the main data table page looks:
If I click 'Add New', it works:
(the 'Save' option works there too. Data posts to the server and will list on the main portfolio page)
Clicking Edit for any of the entries does not work, it gets this far:
The 'Loading Project...' text comes from the render() call for this page, as is shown in the code posted at the top of this post.
The page is supposed to look exactly like the 'Create' page (2nd screenshot), but with the title being 'Edit', and with input values populated from the given data.
The solution was absurd, but may certainly help others...
The renderCreateForm() method (as shown in Update 1 of the post) was not working because of the .toDateString() method I was using in a few of the inputs.
I changed it to .toString() and now everything works.
For example, with an input like this:
<input className="form-control" type="date" name="ProjectDate" defaultValue={projectData.creationDate.toDateString()} required />
I changed it to this:
<input className="form-control" type="date" name="ProjectDate" defaultValue={projectData.creationDate.toString()} required />
Note the defaultValue property of the input.
Repeat for all cases of .ToDateString(), and it now works,
Amazing that this didn't bring up an error anywhere. I thought Typescript and all these various frameworks were supposed to get around the issue of Javascript silently failing like that. This has been my longest and most time-wasting 'silent fail error' ever, by a very long margin.

How do I reduce the amount of markup in my reactjs/JSX

I want to cleanup my JSX to separate the presentation markup from the functional markup/DOM elements.
I have a form which will be the top-level of my component hierarchy. Underneath there will be several inputs and form elements to make up the complete component hierarchy. There will also be a fair amount of twitter bootstrap "cruft" that is mainly there to control presentation.
For example:
render: function() {
return (
<form role="form" className="form-horizontal">
<div className="form-group">
<label htmlFor="homePrice">Home Price</label>
<div className="input-group input-group-lg">
<span className="input-group-addon">$</span>
<input id="homePrice"
className="form-control"
onChange={this.priceChange}
value={this.state.homePrice}
/>
</div>
</div>
<div className="form-group">
<label htmlFor="depositAmount">Deposit Amount</label>
<div className="input-group input-group-lg">
<span className="input-group-addon">$</span>
<input id="depositAmount"
className="form-control"
onChange={this.depositChange}
value={this.state.depositAmount}
/>
</div>
</div>
... snip ...
Ideally I'd have separate components for <form> and then individual child components for each of the <inputs>, without the label tags or <div className="form-group"> and <span className="input-group-addon"> and be able to insert those into various points in the HTML document and maintain component hierarchy.
So how can I accomplish this and maintain the component hierarchy. Changing values in the inputs affects state in the parent, top-most component.
Thanks.
You can do this quite effectively via composition. Just how customizable you want your custom components to be is up to you; with more props and conditional logic, you gain more customizability.
I'll often start by writing the markup/JSX I wish I could use:
render: function() {
return (
<Form horizontal>
<Input id="homePrice"
addon="$" label="Home Price"
onChange={this.priceChange}
value={this.state.homePrice} />
<Input id="depositAmount"
addon="$" label="Deposit Amount"
onChange={this.depositChange}
value={this.state.depositAmount} />
</Form>
);
}
Then you can start implementing the components as necessary to make it work:
var Form = React.createClass({
render: function() {
var direction = "vertical";
if (this.props.horizontal) direction="horizontal";
return (
<form role="form" className={"form-" + direction}
onSubmit={this.props.onSubmit}>
{this.props.children}
</form>
);
}
});
var Input = React.createClass({
render: function() {
return (
<div className="form-group">
<label htmlFor={this.props.id}>{this.props.label}</label>
<div className="input-group input-group-lg">
<span className="input-group-addon">{this.props.addon}</span>
<input id={this.props.id}
className="form-control"
onChange={this.props.onChange}
value={this.props.value} />
</div>
</div>
);
}
});
By utilizing this.props.children, getDefaultProps, the spread operator (e.g. transferring props to a new element via <someElement {...this.props} />), and conditionals, you can create really nice abstractions for more complex components. Additionally, propTypes allows to you specify what properties a component takes, serving as documentation and a way to catch runtime errors in development.
You might consider checking out the source for React Bootstrap to see how they implement composite components to wrap more complex Bootstrap HTML; for example, here's what you'd write to use a tab component:
<TabbedArea defaultActiveKey={2}>
<TabPane eventKey={1} tab="Tab 1">TabPane 1 content</TabPane>
<TabPane eventKey={2} tab="Tab 2">TabPane 2 content</TabPane>
</TabbedArea>
If you're more interested in describing the structure of your forms and then letting a library create the elements automatically, you might look into projects like React Forms:
function Person(props) {
props = props || {}
return (
<Schema name={props.name} label={props.label}>
<Property name="first" label="First name" />
<Property name="last" label="Last name" />
</Schema>
)
}
var family = (
<Schema>
<Person name="mother" label="Mother" />
<Person name="father" label="Father" />
<List name="children" label="Children">
<Person />
</List>
</Schema>
)
React.renderComponent(
<Form schema={family} />,
document.getElementById('example'))

Resources