I'm using React and Meteor to generate a table of information about items in a catalog. I also made a form so that users can add new items. Each item has a name, price and manufacturer. This works perfectly fine when I make all the inputs text areas, but I want to make the manufacturer input a with tags generated according to a Meteor collection called Manufacturers.
Form = React.createClass({
propTypes: {
manufacturer: React.PropTypes.object.isRequired
},
renderOptions(){
return(<option value={this.props.manufacturer.name}>{this.props.manufacturer.name}</option>);
},
submitItem(event){
event.preventDefault();
var name=React.findDOMNode(this.refs.nameInput).value.trim();
var price=React.findDOMNode(this.refs.priceInput).value.trim();
var manufacturer=React.findDOMNode(this.refs.manufacturerInput).value.trim();
Items.insert({
name: name,
price: price,
manufacturer: manufacturer,
});
React.findDOMNode(this.refs.nameInput).value="";
React.findDOMNode(this.refs.priceInput).value="";
},
render(){
return(
<footer>
<form className="new-item" onSubmit={this.submitItem}>
<input type="text" ref="nameInput" placeholder="Name" required />
<input type="text" ref="priceInput" placeholder="Price" required />
<button onClick={this.submitItem}>ADD</button>
</form>
<select ref="manufacturerInput" defaultValue="" required />
<option value="" disabled>Manufacturer</option>
{this.renderOptions()}
</select>
</footer>
);
}
});
How do I make this work? I've been pulling my hair out all day trying to figure it out, but Meteor just keeps telling me "Expected corresponding JSX closing tag for (38:8)". What am I doing wrong here? It works just fine when they're all but this destroys my entire app.
You accidentally closed your select element here:
<select ref="manufacturerInput" defaultValue="" required />
Remove the /.
Related
I recently came across a situation where I was working on a huge form with atleast 60 fields and I wanted that form to only submit if all fields were filled and if not, I wanted to show a custom message (Sweetalert) for every field not filled.
For example, If first name was left empty, show the message "Please enter your first name", If country of residence was not selected, show them the message that "Please select your country of residence" so on and so forth.
While I was writing tons of if and else statements to match every field using document.getElementById(), this thought of not doing things right came into my mind. I tried searching the web for this but was unable to find a suitable way of doing such things. Can anyone suggest me a better way rather then writing if else statements of 100 lines ?
By adding a specific class to your form controls you'd be able to retrieve them and iterate through them in order to check which ones are not filled.
Let's say this is your form:
<form id="myForm" name="myForm" novalidate>
<div>
<label for="control_1">Label_1:</label>
<input type="text" id="control_1" name="control_1" class="control" />
</div>
<div>
<label for="control_2">Label_2:</label>
<input type="text" id="control_2" name="control_2" class="control" />
</div>
<div>
<label for="control_3">Label_3:</label>
<input type="text" id="control_3" name="control_3" class="control" />
</div>
<div>
<label for="control_4">Label_4:</label>
<select id="control_4" name="control_4" class="control">
<option value="option_1">Option 1</option>
<option value="option_2">Option 2</option>
<option value="option_3">Option 3</option>
</select>
</div>
<div>
<input type="submit" value="Submit!" />
</div>
</form>
Then you can use the .control class to retrieve all controls and check them:
function onSubmit(e) {
e.preventDefault();
const controls = document
.getElementById("myForm")
.querySelectorAll(".control");
controls.forEach(control => {
if (!isControlFilled(control)) {
console.log(control.id);
// Do whatever you want with control's id
}
});
}
// This is just for illustrative purposes
// Should be adapted to cover all control types
function isControlFilled(control) {
return control.value ? true : false;
}
I have some tags that display text they conditionally render <input /> tags by checking whether the edit state is true or false. When true, instead of showing text, I render an <input /> tag to make inline edit.
Everything works well. The only problem is, when one <button> tag changes the edit state to true, then, instead of showing input field for editing where Edit was clicked, every tag renders their input field.
How do I limit this rendering of input field for only those tags from where the edit state was changed by the Edit button click?
My code:
const [ edit, setEdit ] = useState(false);
const isEdit = edit;
<div>
<p>{ !isEdit ? (<span>Email: {userProfile.email} <button onClick={e=>setEdit(!edit)}>Edit</button></span>) : (<span>Email:
<input type="text" placeholder="email"
name="email" onChange={e=>setEmail(e.target.value)}/>
<button type="submit" onClick={addUserEmail}>Save</button></span>
)}
</p>
<p>About: { !isEdit ? (<span> {userProfile.about} <button onClick={e=>setEdit(!edit)}>Edit</button>
</span>) :
(<span>
<input type="text" placeholder="about"
name="about" onChange={e=>setAbout(e.target.value)}
/>
<button type="submit" onClick={addUserAbout}>Save</button>
</span>)
)}
</p>
</div>
There are a couple of solutions, but the cleanest way would probably be to separate those editable fields into their own component since each of them has its own state.
For example, you can create a generic EditableField component similar to this one:
function EditableComponent({defaultIsEditing = false, renderText, renderInput}) {
const [ isEditing, setIsEditing ] = useState(defaultIsEditing);
if(!isEditing){
//Non-edit mode
return (<span> {renderText()} <button onClick={e=>setEdit(!edit)}>Edit</button></span>);
}
//Edit mode
return renderInput();
}
Then use it as this:
<div>
<EditableComponent
renderText={() => <>Email: {userProfile.email}</>}
renderInput={() => (<span>Email:
<input type="text" placeholder="email" name="email" onChange={e=>setEmail(e.target.value)}/>
<button type="submit" onClick={addUserEmail}>Save</button>
</span>)}
/>
{/* ...repeat for all fields */}
</div>
This solution ensures that you don't repeat the same logic over and over. With two fields you might be okay just making two state variables (e.g. isEdit1, isEdit2), but the more you add the more cumbersome it will become.
Another alternative would be to store the name of the input you're editing as state, this will ensure that only one field is edited at a time, but you need to take care of saving old fields when starting to edit new ones
I am trying to add one cascaded form(inner) field dynamically on click of a button +Add Content. The array for the field is getting updated but the view is still same.
However, when I try to add the outer field on click of a button +Add Heading dynamically its getting added with no issues.
Below is the stackblitz url for reference. Thanks in advance.
https://stackblitz.com/edit/react-utjwsu?embed=1&file=index.js
You're only ever rendering one Content field, and the {this.AddContentInput} isn't valid syntax. You can edit the AddContentBox method to render all of the Content fields:
Original:
...
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Content</Label>
<input className="form-control" type="text"/>
</FormGroup>
{this.AddContentInput}
...
Replaced with:
...
{ this.AddContentFields(element) }
...
and
AddContentFields(element) {
return element.Content.map(content => {
return (
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Content</Label>
<input className="form-control" type="text"/>
</FormGroup>
);
})
}
Here's an edited version of the app with my changes: https://stackblitz.com/edit/react-lcy2te
Your code is working fine and the state is updating perfectly. The problem is that the Content part is added only once in your code. I just used a function which you already added in it
{this.addContentTextBox(element.Content)}
Just replace the AddContentBox function with this:
AddContentBox(){
return this.state.content.map((element,i)=>(
<div className="header-content" key={i} >
<div className="heading-content-wrapper">
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Heading</Label>
<input className="form-control" type="text"/>
</FormGroup>
{this.addContentTextBox(element.Content)}
{this.AddContentInput}
<FormGroup>
<Button color="success" onClick={this.AddContentInput.bind(this,i)}>+ Add Content</Button>
</FormGroup>
</div>
</div>
))
}
You have the code in that file, but i think you forgot to add it in the function.
Hope it helps :)
You are mixing the content variable. In your function AddContentBox() you are using this.state.content.map(...)
this.state.content is an array of object like {Heading: '', Content: [{value: ''}]}
BUT in your function AddContentInput() your are pushing an object inside an object of this array contents[index].Content.push({value:''})
Depending on your needs you should either push in the root array contents.push({Heading: '', Content: [{value: ''}]} OR iterate the right array in your function AddContentBox() and use this.state.content[0].Content.map(...)
So I’ve spent the last few weeks learning react and now I’m trying to do so with firebase.
Right now, I am trying to make it possible to edit and delete a blog post. I’ve made a few similar things over the last few weeks with express, rails etc so I thought I got a pretty good handle on it so right now I’m trying to compare and know the differences between them.
Most of the functions have been easily refactored to work with what I’m doing now so I’m not sure exactly how to tweak this one to work here or if it won’t work at all which would then turn to how do I properly do it here.
The original code I used last time that I tried to edit for this was:
getInitialState(){
return {editable: false}
},
handleEdit(){
if( this.state.editable){
var title = this.refs.title.value;
var article= this.refs.article.value;
var id = this.props.post.id;
var post = {id: id, title: title, article: article};
this.props.handleUpdate(post);
}
this.setState({editable: !this.state.editable})
},
render: function(){
var title= this.state.editable? <input className= "form-control" type='text' ref='title' defaultValue={this.props.post.title}/>:
<h3>{this.props.post.title}</h3>;
var article= this.state.editable ? <input className= "form-control" type='text' ref='article' name="logEntryEdit" defaultValue={this.props.post.article}/>:
<p className="postText">{this.props.post.article}</p>;
return(
<div >
<div className="text-center postTitle">
{title}
</div>
<hr/>
<br/>
<div className="text-center" id="postText">
{article}
</div>
<br/>
<hr/>
<button className="btn btn-danger delete-button" onClick={this.props.handleDelete}>Delete</button>
<button className="btn btn-primary" onClick={this.handleEdit}>{this.state.editable ? 'Submit' : 'Edit'}</button>
</div>
)
Naturally I edited the item names and such. How I set up my edit page was by first passing the selected post via the id that firebase generates into a single view page and then pass it in into the edit page. The delete should be from that view page as well.
This is the edit where I tried to put in the above function:
constructor(props){
super(props);
this.state = {
title: '',
body: '',
post:{},
};
}
componentDidMount(){
database.ref(`posts/${this.props.match.params.postId}`)
.on('value', snapshot => {
this.setState({post: snapshot.val()});
});
this.postId= this.props.match.params.postId;
}
//edit function goes here
render() {
return (
<div className="container">
<form onSubmit={this.onHandleUpdate}>
<div className="form-group">
<input value={this.state.post && this.state.post.title} className="form-control" type="text" name="title" ref="title" placeholder="Title" onChange={this.onInputChange}/>
</div>
<br/>
<div className="form-group">
<input value={this.state.post && this.state.post.body} className="form-control" type="text" name="body" ref="body" placeholder="Body" onChange={this.onInputChange}/>
<br/>
</div>
<button className="btn btn-success">Submit</button>
</form>
</div>
);
}
When I tried adding it in I also got a
A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
It linked me to the docs which I went over as well.
Can you please tell me how to make the original code I had fit in and work for this time or how it is supposed to be done so I can compare and understand the difference?
This error stems from the fact that you are stating that the input fields are updated via a this.onInputChangefunction and yet that function is not defined anywhere. This means there is no way to update the inputs and react does not like that. either define your this.onInputChange function or switch to using refs (when you use ref react call that an uncontrolled component).
Firstly, I should say that I have only attacked Angular.js for 2 days now so I may be approaching this whole thing wrong.
My example "Person" object (or model if I understand) has two properties, Firstname and Title. I have two fields <select> for title and <input type="text" /> for name. The Firstname binding works fine and I am happy, but the title is confusing me.
I am binding an object array with Code and Desc which populates fine. But when I want to get (on submit) or set (on load) it does not work because the binding uses the entire title object.
http://jsfiddle.net/qm7E7/11/
(Click on Next to display the serialized object.)
HTML
<div ng-app ng-controller="PersonCtrl">
<form name="myForm" ng-submit="submit()">
Title: <select ng-model="Person.Title" ng-options="t.Desc for t in TitleList">
<option style="display: none" value="">Please select</option>
</select>
<br/>
Firstname: <input type="text" ng-model="Person.Firstname" />
<br/>
<input type="submit" value="Next" />
<br/>
<div id="obj"></div>
</form>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js">
JS
// Person controller
function PersonCtrl($scope) {
$scope.TitleList = [{Code: 'MR', Desc: 'Mr'},
{Code: 'MRS', Desc: 'Mrs'}]
$scope.Person = {
Firstname: 'dave',
Title: 'MR'
};
$scope.submit = function () {
$("#obj").text(JSON.stringify($scope.Person));
};
}
Am I misunderstanding the whole concept or what. I should also say that I don't want to use the whole MVC routing concept. Just the objects and "2 way binding".
The select input is a very special puppy in angular.
Here's an updated fiddle:
http://jsfiddle.net/qm7E7/16/
You could provide the data as an object, where the keys are the codes and the values are the description:
$scope.TitleList = {
'MR' : 'Mr',
'MRS' : 'Mrs'
};
And in your html:
<select ng-model="Person.Title" ng-options="code as desc for (code, desc) in TitleList">
the syntax is a little tricky. You can read more about it in the angular docs, especially the comments. http://docs.angularjs.org/api/ng.directive:select
You need to specify the value
<select ng-model="Person.Title" ng-options="t.Code as t.Desc for t in TitleList">
http://jsfiddle.net/qm7E7/19/