// initial state and other properties
var ReviewControl = React.createClass({
getInitialState: function() {
return {
name:'',
feedback:'',
movie:'',
reviews:[]
};
},
onChangeName:function(el){
this.setState({name:el.target.value})
},
onChangeFeedback:function(el) {
this.setState({feedback:el.target.value})
},
onChangeMovie:function(el){
this.setState({agent:el.target.value})
},
submitReview:function(el) {
//preventing default for the form
el.preventDefault();
//storing data into the reviews array created earlier
this.state.reviews.push(
{name:this.state.name, feedback:this.state.feedback, movie:this.state.movie});
this.setState({name:'', feedback:''});
},
render: function() {
var options = this.props.list.map(function(agent){
return (<option key={agent.id} value={agent.agentName}>{agent.agentName}</option>);
});
//rendering the form for feedback
return (
<div>
//creating form
<form onSubmit = {this.submitReview}>
<lable> Agent Name : </lable>
<input type="text" placeholder='Enter The Agent Name' value={this.state.name} onChange={this.onChangeName} />
<br/><br/>
<lable> Feedback : </lable>
<input type="text" placeholder='Enter Your Feedback' value={this.state.feedback} onChange={this.onChangeFeedback} />
<br/><br/>
<select onChange={this.onChangeMovie}>
{options}
</select>
<br/><br/>
<input type="submit" value="Submit"/>
</form>
//collecting the reviews
<ReviewCollection reviews = {this.state.reviews} />
</div>
)
}
});
// collecting all the reviews from the user and storing it
var ReviewCollection = React.createClass ({
render:function () {
var reviews = this.props.reviews.map(function(review) {
return <Review key={review.id} movie = {review.movie} name = {review.name} feedback = {review.feedback} />
});
return (<div> {reviews} </div>)
}
});
//showing the stored values from the array review
var Review = React.createClass({
render:function(){
return <div>
<span> Agent Name </span>
{this.props.name}
<br/>
<span> Movie Name </span>
{this.props.movie}
<br/>
<span> Feedback </span>
{this.props.feedback}
<br/>
</div>
}
});
// rendering the form
var finalReview = <ReviewControl list={agentList} />;
ReactDOM.render(finalReview, document.getElementById('myid'));
You aren't setting the new review state in submitReview. Just pushing into the old array isn't actually updating state. You have to explicitly update it.
this.state.reviews.push({
name: this.state.name,
feedback: this.state.feedback,
movie: this.state.movie
});
this.setState({
name: '',
feedback: '',
reviews: this.state.reviews
});
Another small thing I noticed is that you're using el as the argument for events. Typically it's just e or event. el is usually short for element, usually a dom node.
Related
What am trying to do is to be able to add an input field dynamically.
So for example, once you fill your first hobby, then you decide to add another one. you click on add and a new input field is shown.
The input field is showing for split second then disappearing.
Code:
class App extends React.Component {
state = {
Hobbies: []
}
addHobby = () => {
this.setState(prevState => ({ Hobbies: [...prevState.Hobbies, ''] }))
}
handleChange(i, event) {
let Hobbies = [...this.state.Hobbies];
Hobbies[i] = event.target.value;
this.setState({ Hobbies });
}
removeClick(i) {
let Hobbies = [...this.state.Hobbies];
Hobbies.splice(i, 1);
this.setState({ Hobbies });
}
render() {
const widthStyle = {
width: '15rem'
};
return (
<div className="App">
<form >
<label>
Hobbies:
<input type="text" name="hobby" />
</label>
<br /><br />
{
this.state.Hobbies.map((el, i) => {
return (
<div key={i}>
<input type="text" value={el || ''} onChange={this.handleChange.bind(this, i)} />
<input type='button' value='remove' onClick={this.removeClick.bind(this, i)} />
</div>
)
})
}
<button onClick={this.addHobby.bind(this)}>ADD Hobby</button>
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));
The problem is you are clicking inside a form which is why your page is getting refreshed(form submits) and you lose the state
addHobby = (e) => {
e.preventDefault() //<-----
this.setState({ Hobbies: [...this.state.Hobbies, ''] })
}
CodeSandbox
An alternative to using e.preventDefault() is to mark the button as being of type="button" the default inside a <form> element is type="submit" which automatically submits the form/refreshes the page.
<button type="button" onClick={this.addHobby.bind(this)}>ADD Hobby</button>
Setting `type="button" indicates that the button is just there to trigger some JavaScript action, not to submit the form.
JSFiddle: https://jsfiddle.net/morahood/cp569zg6/38/
When I delete a child ServiceItem component from the ServiceList component, I am running into an issue where the last ServiceItem in the list is removed instead of the targeted ServiceItem. The JSFiddle link above will help understand the issue, just make sure you enter in some identifiable information in the input fields so you can see what is getting deleted.
How do I fix my application so that the respective component is deleted?
var ServiceForm = React.createClass({
render: function() {
return (
<form onSubmit={ this.handleFormSubmission } >
{ this.renderServiceItemList() }
<div className="btn btn-default btn-sm" onClick={ this.addServiceItem }>+ Append New Service Item</div>
<button type="submit" className="btn btn-success btn-lg pull-right">Submit</button>
</form>
);
},
getInitialState: function() {
return ({
serviceItems: [this.renderServiceItem]
});
},
handleFormSubmission: function(event) {
event.preventDefault();
var data = this.refs.service_item_list.getAllServiceItems();
var json = {
"service_request" : {
"services" : data,
"additional_recipients" : 'test#example.com',
"comments" : 'Hello world!'
}
};
console.log(json);
},
addServiceItem: function(event) {
var copy = this.state.serviceItems.slice();
copy.push(this.renderServiceItem);
this.setState({
serviceItems: copy
});
},
renderServiceItem: function(item, i) {
return (
<ServiceItem removeServiceItem={ this.removeServiceItem } data={item} key={i} ref={"service_item_" + i} />
);
},
removeServiceItem: function(event) {
var index = parseInt(event.target.value, 10);
var copy = this.state.serviceItems.slice();
copy.splice(index, 1);
this.setState({
serviceItems: copy
});
},
renderServiceItemList: function() {
return (
<ServiceItemList serviceItems={ this.state.serviceItems }
renderServiceItem={ this.renderServiceItem }
removeServiceItem={ this.removeServiceItem }
ref="service_item_list" />
);
}
});
var ServiceItemList = React.createClass({
render: function() {
var content;
if (this.props.serviceItems.length < 1) {
content = <div className="empty-service-list">There are no service items, click on append new service item below!</div>;
} else {
content = this.props.serviceItems.map(this.props.renderServiceItem);
}
return (
<div>
{ content }
</div>
);
},
getAllServiceItems: function() {
var data = [];
for (var ref in this.refs) {
data.push(this.refs[ref].serializeItem());
}
var merged = [].concat.apply([], data);
return(merged);
}
});
var ServiceItem = React.createClass({
render: function() {
return (
<div className="row">
<div className="col-sm-3">
<div className="form-group service-item">
<label>Service Item </label>
<select multiple ref="service_types" className="form-control">
<option>Oil Change</option>
<option>Tire Rotation</option>
<option>New Wiper Blades</option>
</select>
</div>
</div>
<div className="col-sm-3">
<div className="form-group">
<label>Customer </label>
<select ref="customer" className="form-control">
<option>Troy</option>
<option>Dave</option>
<option>Brandon</option>
</select>
</div>
</div>
<div className="col-sm-3">
<div className="form-group">
<label>Manufacturer </label>
<div className="input-group">
<input ref="manufacturer" type="text" className="form-control" />
</div>
</div>
</div>
<div className="col-sm-3">
<div className="form-group">
<label>Model </label>
<div className="input-group">
<input ref="model" type="text" className="form-control" />
</div>
<a href="#" onClick={ this.props.removeServiceItem }>
<span aria-hidden="true" className="remove-fields" onClick={ this.props.removeServiceItem }>×</span>
</a>
</div>
</div>
</div>
);
},
getInitialState: function() {
return({
service_types: [],
customer: '',
manufacturer: '',
model: ''
});
},
serializeItem: function() {
var customer = this.refs.customer.value;
var manufacturer = this.refs.manufacturer.value;
var model = this.refs.model.value;
var selected = this.getSelectedServiceItems();
var services = this.getSelectedServiceItems().map(function(item) {
return (
{
"service" : {
"service_type" : item,
"customer" : customer,
"manufacturer" : manufacturer,
"model" : model
}
}
)
});
return(services);
},
getSelectedServiceItems: function() {
var select = this.refs.service_types;
var values = [].filter.call(select.options, function (o) {
return o.selected;
}).map(function (o) {
return o.value;
});
return(values);
}
});
ReactDOM.render(
<ServiceForm />,
document.getElementById('container')
);
Your issue is with your key={i}.
React lists requires the key of an item to be unique to the item, regardless of the position of the item in the list. This allows react to do smart management of the list in case of updates.
React will NOT render the following update correctly:
From: To:
name: key: name: key:
Bill 0 Bill 0
Charlie 1 Dave 1
Dave 2
Because react thinks that the Charlie record is unchanged (because the key is unchanged).
I would advise you to put some sort of ID from the service-item retrieved as key.
On name, e.g.
From: To:
name: key: name: key:
Bill Bill Bill Bill
Charlie Charlie Dave Dave
Dave Dave
React will render the update correctly in this case (because key is unique to the item).
Playing with react, trying to build a simple chat app that will be connected to firebase.
I have one container - ChatApp - and one component UserInput.
ChatApp ->
import React from 'react';
import UserInput from '../components/UserInput';
export default React.createClass({
getInitialState: function() {
return {
chatting: false
};
},
render: function() {
return <div>
<UserInput
startChat={this.chatCanStart}
/>
</div>;
},
chatCanStart: function(recipient) {
console.log(recipient);
this.setState({
chatting: true
})
}
});
UserInput ->
import React from 'react';
export default React.createClass({
getInitialState: function() {
return {
username: '',
recipient: 'asgasg'
};
},
handleUsernameChange: function(event) {
let text = event.target.text;
this.setState({
username: text
});
},
handleRecipientChange: function(event) {
let text = event.target.text;
this.setState({
recipient: text
});
},
handleStartChat: function() {
if (this.state.username !== '' && this.state.recipient !== ''){
console.log(this.state.recipient);
this.props.startChat(this.state.recipient);
}
},
render: function() {
return <div className="panel panel-default">
<div className="panel-heading"> Welcome to the chat app done in React </div>
<div className="panel-body">
<div className="input-group">
<span className="input-group-addon"> Username </span>
<input type="email" className="form-control" value={this.state.username} onChange={this.handleUsernameChange} />
</div>
<br />
<div className="input-group">
<span className="input-group-addon"> Recipient </span>
<input type="email" className="form-control" value={this.state.recipient} onChange={this.handleRecipientChange} />
</div>
<br />
<button type="button" className="btn btn-primary" onClick={this.handleStartChat}> Chat now </button>
</div>
</div>;
}
});
When I change the Username and Recipient fields and then click on the Chat now button, I am expecting the chatting state in the ChatApp container to change to true and also want to log the passed recipient.
But I get 'undefined', why is that? I get 'undefined' even in the console.log in the UserInput component. Even though the inputs value in the form is being changed just fine.
What am I doing wrong?
Thanks.
To get value from input you need to use .value property instead of .text
handleUsernameChange: function(event) {
let text = event.target.value;
this.setState({
username: text
});
},
handleRecipientChange: function(event) {
let text = event.target.value;
this.setState({
recipient: text
});
},
Example
I'm new to meteor and react. Let's say I have this in my react component:
getMeteorData() {
var myForm = ProjectForm.findOne({_id:this.props.router.params._id});
var projects = ProjectForm.find({});
return {myForm:myForm,projects:projects};
// doing a console.log(myForm); would give you something like
/*
input1:my text 1
input2:some other text
input3:something else
etc....
*/
},
renderListProjects() {
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a href={Meteor.absoluteUrl()+'project/' + projectform.username +'/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
getInitialState() {
return Projects.findOne({this.props.router.params._id}, {sort: {createdAt: -1}});
},
render() {
return (
<div>
<ul>{this.renderListProjects()}</ul>
<form>
<span>Hello this is some text</span>
<input type="text" ref="input1" />
<p>Blah blah this is boring</p>
<input type="text" ref="input2" />
<img src="image-of-a-kangaroo.png" />
<input type="text" ref="input3" />
<ul>
<li>Buy brocolli</li>
<li>Buy oregano</li>
<li>Buy milk</li>
</ul>
<input type="text" ref="input4" />
...
<textarea ref="input100"></textarea>
<input type="text" ref="input101" />
<p><strong>Yes, I like pizza!</strong> But my porcupine gets sick eating pizza.</p>
...
</form>
</div>
);
What I want to do is assign the values of this.data.myForm to each of the form fields in the render() function. But when I do something like <input type="text" ref="input1" value={this.data.myForm.input1} />, and I go to my web browser and put my cursor on that field, I am NOT able to modify the value. Pressing the keys on my keyboard will not change the value of that input field. Additionally, I have about 250 input fields in this html form. I really don't want to do any data entry. I would much rather just use some kind of loop to iterate through the this.data.myForm and assign it to the corresponding form fields. But when I tried to do this, I get problems about the DOM not being found or it's not loaded. So I tried writing some code in componentDidMount(), but then I got other errors.
Can anyone point me in the right direction on how to efficiently bind all my this.data.myForm data to my form fields AND allow me to edit the form fields after?
Additional Requirements
If someone clicks on link1 from the renderListProjects then clicks on link2 from the renderlistProjects, then the form must show the value of link2's projectform._id.
In the DOM, an href="project/_id" attribute must exist for SEO and for WCAG compliance.
Attempts
I tried to redefine renderListProjects as
renderListProjects() {
var pj = this
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a onClick={pj.click(projectform._id)} href={Meteor.absoluteUrl()+'project/' + projectform.username +'/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
click(id) {
var currentApp = ProjectForm.findOne({_id:id}, {sort: {createdAt: -1}});
this.setState({input36:input36});
},
But when I run my meteor+react project, my browser crashes because some kind of infinite loop is happening.
What you created is what React calls an controlled input. This means that React has declared your value in the inputs to correspond to whatever this.data.myForm.input1 points to.
What you need to do in order to be able to change an input field is to add an onChange handler that takes care of updating the value. The documentation is quite straight forward but I added a small example below to illustrate it.
class Form extends React.Component {
constructor() {
super();
this.state = {
myForm: {
inputs: [
{value: 1}, {value: 2}, {value: 3}
]
}
};
}
onChange = (index, event) => {
let inputs = this.state.myForm.inputs.slice(0);
inputs[index] = {
value: parseInt(event.target.value, 10)
};
this.setState({
myForm: {
inputs: inputs
}
})
}
render() {
return (<form>
{this.state.myForm.inputs.map((input, index) => {
return (<input onChange={this.onChange.bind(null, index)} value={input.value} key={index} />);
}, this)}
</form>);
}
};
ReactDOM.render(<Form />, document.querySelector('#c'));
This code solved the problem
getMeteorData() {
var myForm = ProjectForm.findOne({_id:this.props.router.params._id});
var projects = ProjectForm.find({});
return {myForm:myForm,projects:projects};
// doing a console.log(myForm); would give you something like
/*
input1:my text 1
input2:some other text
input3:something else
etc....
*/
},
clickLoadForm(appId)
{
var currentApp = Projects.findOne({appId}, {sort: {createdAt: -1}});
var state = new Object();
var refs = this.refs;
Object.keys(refs).map(function(prop,index){
state[prop] = typeof currentApp[prop] == 'undefined' ? "" : currentApp[prop];
});
this.setState(state);
},
handleChange: function(e) {
if(!e.target.id) return;
if(typeof e.target.id == 'undefined') return;
var state = new Object();
state[e.target.id] = e.target.value;
this.setState(state);
},
renderListProjects() {
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a onClick={_this.clickLoadForm.bind(_this,projectform._id)} href={Meteor.absoluteUrl()+'project/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
getInitialState() {
return Projects.findOne({this.props.router.params._id}, {sort: {createdAt: -1}});
},
render() {
return (
<div>
<ul>{this.renderListProjects()}</ul>
<form>
<span>Hello this is some text</span>
<input type="text" ref="input1" id="input1" onChange={this.handleChange} />
<p>Blah blah this is boring</p>
<input type="text" ref="input2" id="input2" onChange={this.handleChange} />
<img src="image-of-a-kangaroo.png" />
<input type="text" ref="input3" id="input3" onChange={this.handleChange} />
<ul>
<li>Buy brocolli</li>
<li>Buy oregano</li>
<li>Buy milk</li>
</ul>
<input type="text" ref="input4" id="input4" onChange={this.handleChange} />
...
<textarea ref="input100" id="input100" onChange={this.handleChange}/>
<input type="text" ref="input101" id="input101" onChange={this.handleChange} />
<p><strong>Yes, I like pizza!</strong> But my porcupine gets sick eating pizza.</p>
...
</form>
</div>
);
To summarize, my changes were:
created a renderListProjects() function
created the clickLoadForm() function and used it in the renderListProjects()
created the handleChange() function and made sure it's references via onChange for each element in render()
made sure every element in render() has an id that's the same as the ref
I'm playing around with a Fluxxor tutorial (example at the very top) and there's a simple todo list built with React. It's very basic and I wanted to add a simple validation to get better understand of data binding.
var Application = React.createClass({
mixins: [FluxMixin, StoreWatchMixin("TodoStore")],
getInitialState: function() {
return { newTodoText: "" };
},
getStateFromFlux: function() {
var flux = this.getFlux();
return flux.store("TodoStore").getState();
},
render: function() {
var todos = this.state.todos;
return (
<div>
<ul>
{Object.keys(todos).map(function(id) {
return <li key={id}><TodoItem todo={todos[id]} /></li>;
})}
</ul>
<form onSubmit={this.onSubmitForm}>
<input type="text" size="30" placeholder="New Todo"
value={this.state.newTodoText}
onChange={this.handleTodoTextChange} />
<input type="submit" value="Add Todo" />
</form>
<button onClick={this.clearCompletedTodos}>Clear Completed</button>
</div>
);
},
So if I want to disable submit if the input field is empty, I can compare the value of input with the initial state:
<input type="submit" value="Add Todo" disabled={!#state.newTodoText}/>
Now I want to disable Clear Completed until at least one TodoItem is marked as completed. When this happens this.state.todos is updated with a with a completed key set to true and it's available in my application component:
Object {todo: Object}
todo: Object
complete: true
id: 1
text: "text"
How I should handle this logic? Thanks in advance.
You can write a function which returns the number of completed todos in your object.
countCompleted: function(todos) {
var todoKeys = Object.keys(todos);
var completed = todoKeys.filter(function(key) {
var todo = todos[key];
return todo.complete;
});
return completed.length;
}
Then call that function from render to conditionally disable the button.
<button
onClick={this.clearCompletedTodos}
disabled={this.countCompleted(todos) > 0}>
Clear Completed
</button>