I am trying to make a todo app in react. But it keeps double posting. I made an edit function and after that it keeps double posting. I have followed the example of the guide. But I can not find the error.
<!DOCTYPE html>
<html>
<head>
<title>EASY SHIT</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="../../build/react.js"></script>
<script src="../../build/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.min.js"></script>
<style>
body{
margin-top:30px;
}
a.delete{
color: red;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<div id="app"></div>
</div>
</div>
</div>
<script type="text/babel">
var App = React.createClass({
getInitialState: function (){
return{
text: '',
isEdit: 0,
todos:[
{
id: 1,
text: 'meeting at work'
},
{
id: 2,
text: 'Walk the dog'
},
{
id: 3,
text: 'Eat candy'
}
]
}
},
render: function(){
return(
<div>
<TodoForm
{...this.state}
changeText={this.handleChangeText}
onTodoUpdate={this.handleTodoUpdate}
onTodoAdd={this.handleTodoAdd} />
<TodoList
{...this.state}
editTodo={this.handleTodoEdit}
deleteTodo={this.handleTodoDelete}/>
</div>
)
},
handleTodoAdd: function(text){
var newTodo = {
id: this.state.todos.length + 1,
text: text
}
this.setState({todos: this.state.todos.concat(newTodo)});
},
handleTodoDelete: function(todo){
var todos = this.state.todos;
for(var i = 0;i <todos.length; i++) {
if(todos[i].id == todo.id){
todos.splice(i, 1);
}
}
this.setState({todos: todos});
},
handleTodoEdit: function(todo){
this.setState({
text: todo.text,
isEdit: todo.id
});
},
handleChangeText: function(text){
this.setState({text: text});
},
handleTodoUpdate: function(todo){
var todos = this.state.todos;
for(var i = 0;i <todos.length; i++) {
if(todos[i].id == todo.id){
todos.splice(i, 1);
}
}
todos.push(todo);
this.setState({todos: todos});
}
});
var TodoForm = React.createClass({
render: function(){
return(
<div>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Todo text</label>
<input type="text" ref="text" value={this.props.text} onChange={this.onChange} className="form-control" />
</div>
</form>
</div>
)
},
onChange: function(e){
this.props.changeText(e.target.value);
},
onSubmit: function(e){
e.preventDefault();
var text = this.refs.text.value.trim();
if(!text){
alert('Please enter something');
return;
}
if(this.props.isEdit){
var updatedTodo = {
id:this.props.isEdit,
text: text
}
this.props.onTodoUpdate(updatedTodo);
} else {
this.props.onTodoAdd(text);
}
this.props.onTodoAdd(text);
this.refs.text.value= '';
}
});
var TodoList = React.createClass({
render: function(){
return(
<ul className="list-group">
{
this.props.todos.map(todo => {
return <li className="list-group-item" todo={todo} key ={todo.id}>
<span onClick={this.editTodo.bind(this, todo)}> {todo.text}</span> <a onClick={this.OnDelete.bind(this, todo)}className="delete" href="#">x</a></li>
})
}
</ul>
)
},
OnDelete: function(todo){
this.props.deleteTodo(todo);
},
editTodo: function(todo){
this.props.editTodo(todo);
}
});
ReactDOM.render(
<App />,
document.getElementById('app')
);
</script>
</body>
</html>
It looks to me like you aren't tracking whether you are editing an existing item or adding a new one.
If you're editing, then you need to track which one, then replace it or update it in your state, otherwise append to the list. You're just appending, so your app always thinks a new one is being added and hence it looks like it's double posting, but it is actually adding a brand new item that happens to have updated text.
The other option is to call delete and then edit, which will have the same effect.
Related
I am creating one react form which looks like wizard type means on click of next button, it will load another component.
To do with this, i am using javascript iterators. I am updating state based on what iterator returns but i dont understand its not updating state of the component.
Code:
console.clear();
var Email = React.createClass({
render: function() {
return (
<div className="form-group">
<label className="control-label">Email:</label>
<input className="form-control" />
</div>
);
}
});
var Password = React.createClass({
render: function() {
return (
<div className="form-group">
<label className="control-label">Password:</label>
<input className="form-control" />
</div>
);
}
});
var Contact = React.createClass({
render: function() {
return (
<div className="form-group">
<label className="control-label">Contact:</label>
<input className="form-control" />
</div>
);
}
});
var Form = React.createClass({
getInitialState: function(){
this.fit = this.flowIterator([<Email />,<Password />,<Contact />]);
return {
view: this.fit.next(),
submitBtnText: "Next"
};
},
flowIterator: function(array){
var nextIndex = 0;
return {
next: function(){
console.log("nextIndex=",nextIndex);
console.log("array.length-1=",array.length-1);
console.log("nextIndex == array.length-1 =",nextIndex == array.length-1);
return nextIndex == array.length-1 ?
{value: array[nextIndex++], done: true} :
{value: array[nextIndex++], done: false};
}
}
},
onFormSubmit: function(e){
this.setState({
onChange: this.onStateChange()
});
e.preventDefault();
},
onStateChange: function(){
console.log("State change event fired!!");
this.setState({
view: this.fit.next(),
submitBtnText: "Next"
});
console.log("this.state.view.done=",this.state.view.done);
if(this.state.view.done){
this.setState({
submitBtnText: "Login"
});
}
},
render: function() {
return (
<form className="form-horizontal col-xs-6" onSubmit={this.onFormSubmit}>
{this.state.view.value}
<div className="form-group">
<button className="btn btn-success pull-right">{this.state.submitBtnText}</button>
</div>
</form>
);
}
});
ReactDOM.render(
<Form />,
document.getElementById('container')
);
check the code in jsfiddle
Thank you...
I'm trying to work through the React Tutorial and have run into a stumbling block. I have the following HTML
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="./src/favicon.ico" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/css/materialize.min.css" />
<script src="https://npmcdn.com/react#15.3.1/dist/react.js"></script>
<script src="https://npmcdn.com/react-dom#15.3.1/dist/react-dom.js"></script>
<script src="https://npmcdn.com/babel-core#5.8.38/browser.min.js"></script>
<script src="https://npmcdn.com/jquery#3.1.0/dist/jquery.min.js"></script>
<script src="https://npmcdn.com/remarkable#1.6.2/dist/remarkable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/js/materialize.min.js"></script>
<script type="text/babel" src="src/index.js"></script>
<title>React App</title>
</head>
<body>
<div class="container">
<div class="row">
<div id="sidebar" class="col s3">
</div>
<div id="content" class="col s9">
</div>
</div>
</div>
<script type="text/babel">
</script>
</body>
</html>
With the following javascript file.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
var data = [
{ id: 1, author: "Pete Hunt", text: "This is the 1st comment" },
{ id: 2, author: "Jordan Walke", text: "This is *the 3rd* comment." },
{ id: 3, author: "John Doe", text: "This comment is stupid." }
];
var CommentBox = React.createClass({
getInitialState: function() {
return { data: [] };
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({ data: data });
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
I'm a Form.
</div>
);
}
});
var Comment = React.createClass({
rawMarkup: function() {
var markdown = new Remarkable();
var rawMarkup = markdown.render(this.props.children.toString());
return { __html: rawMarkup };
},
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
});
ReactDOM.render(
<CommentBox data={data} />,
document.getElementById('content')
);
I'm using the content created from the Create React App NPM module. When I run the app, I'm told:
Remarkable isn't defined
$ is not defined
Both JQuery and Remarkable are included in my HTML file, and I made sure to put them before my index.js script. Why is it that the app can't find the Types? Is this a React issue, or a Node,js issue? I'm a bit new to web-dev so I'm not sure which component is at fault here or what i'm doing wrong.
Seems like your scripts are loading asynchronously. Waiting for all your scripts to be loadded with jQuery...
(function($){$(function(){
// your react app code here...
})})(jQuery);
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).
I am still learning React and Javascript so thank you for your patience.
I am trying to serialize my form data so I can send it to a Ruby on Rails back end for processing. I am just using vanilla React with no additional depedencies like Flux, Redux, etc.
It seems like my child components are not returning anything and I am not quite sure why.
I have tried:
exposing values through the use of refs (but failed and read that it isn't really a good idea to do that anyway)
exposing a parent method within my child components to gather information about each individual component (what you will see in my jsfiddle).
updating component states through onChange methods and trying to access the states of each child component
My JSFiddle: https://jsfiddle.net/morahood/0ptdpfu7/91/
I am clearly missing a key element of React design patterns here. Am I just way off track? How can I get back on track? I would like to eventually be able to serialize the form data in the following format
{
"service_request" : {
"services" : [
{
"service_item" : ["Oil Change", "New Windshield Wipers"],
"customer" : "Troy",
"manufacturer" : "Ford",
"model" : "F150"
},
{
"service_item" : ["Tire Rotation"],
"customer" : "Dave",
"manufacturer" : "Hyundai",
"model" : "Genesis"
}
]
}
}
Components
var ServiceForm = React.createClass({
render: function() {
return (
<form onSubmit={ this.handleFormSubmission }>
{ this.state.serviceItems.map(function(item) {
return (item);
})}
<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: [<ServiceItem serializeServiceItem={ this.serializeServiceItem } />]
});
},
handleFormSubmission: function() {
console.log("form submitted!");
alert("Serialized Form Data: " + this.serializeFormData());
},
addServiceItem: function(event) {
var serviceItems = this.state.serviceItems;
serviceItems.push(<ServiceItem serializeServiceItem={ this.serializeServiceItem } />);
this.setState({
serviceItems: serviceItems
});
},
serializeServiceItem: function() {
var jsonData = {
"service_item" : this.state.service_items,
"customer" : this.state.customer,
"manufacturer" : this.state.manufacturer,
"model" : this.state.model
}
return (jsonData);
},
serializeFormData: function() {
return( this.state.serviceItems.map(function(item) {
return (item.serializeServiceItem);
}));
}
});
var ServiceItem = React.createClass({
render: function() {
return (
<div className="row">
<div className="col-sm-3">
<div className="form-group">
<label>Service Item </label>
<select multiple name="service_item" selected={ this.state.service_items } 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 name="customer" selected={ this.state.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 name="manufacturer" value={ this.state.manufacturer } onChange={ this.setManufacturer } 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 name="model" value={ this.state.model } onChange={ this.setModel } type="text" className="form-control" />
</div>
</div>
</div>
</div>
);
},
getInitialState: function() {
return({
service_items: [],
customer: "",
manufacturer: "",
model: ""
});
},
setModel: function(event) {
this.setState({ model: event.target.value });
},
setManufacturer: function(event) {
this.setState({ manufacturer: event.target.value });
},
setCustomer: function(event) {
this.setState({ customer: event.target.selected });
},
setServiceItems: function(event) {
this.setState({ service_items: event.target.selected });
}
});
ReactDOM.render(
<ServiceForm />,
document.getElementById('container')
);
Solution
https://jsfiddle.net/morahood/cp569zg6/19/
You "might" be way overcomplicating things here. The DOM element for <form> can actually be treated as an array of all the inner <input> elements. In other words, if you have:
render: function() {
return (
<form ref="form">
...
</form>
);
}
All your input elements can be accessed by:
serialized = {}
for (var i in this.refs.form) {
var input = this.refs.form[i];
serialized[input.name] = input.value;
}
This might not provide you with enough flexibility. A better solution might be to define methods in your component instances that return the input values:
var ServiceForm = React.createClass({
serializeFormData: function() {
return {
foo: this.refs.foo.serialize()
};
},
render: function() {
var foo = this.state.foo;
return (
<ServiceItem data={foo} ref="foo" />
);
}
});
var ServiceItem = React.createClass({
serialize: function() {
return {
model: this.refs.model.value,
...
}
},
render: function() {
var model = this.props.data.model;
return (
<input ref="model" value={model} ... />
);
}
});
If you need multiple service items, you'll probably need to rely on this.props.children to access each component instance rather than on this.refs:
var ServiceContainer = React.createClass({
collectFormData: function() {
return this.refs.form.serialize();
},
renderServiceItem: function(item, i) {
return (
<ServiceItem data={item} key={i} />
);
},
render: function() {
// Assuming you've moved all your state logic into this ServiceContainer
var serviceItems = this.state.serviceItems;
return (
<ServiceForm ref="form">
{serviceItems.map(this.renderServiceItem)}
</ServiceForm>
);
}
});
var ServiceForm = React.createClass({
serialize: function() {
return React.Children.map(this.props.children, function(item) {
return item.serialize();
});
},
render: function() {
return (
<div>{this.props.children}</div>
);
}
});
var ServiceItem = React.createClass({
serialize: function() {
// You can still access your input elements through refs in here
...
},
render: function() {
...
}
});
Note that I'm using React.Children here rather than simply using this.props.children because when there's only a single child, children is not an array (see: https://facebook.github.io/react/tips/children-props-type.html).
You could use react.rb and reactive-record which will do all this for you out of the box. http://reactrb.org
I'm doing the tutorial on the React.js website. Here is my code:
<html>
<head>
<title>Hello React</title>
<script src="http://fb.me/react-0.8.0.js"></script>
<script src="http://fb.me/JSXTransformer-0.8.0.js"></script>
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
/**
* #jsx React.DOM
*/
// The above declaration must remain intact at the top of the script.
// Your code here
var commentsData = [
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
var CommmentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
componentWillMount: function() {
this.loadComments();
},
render: function() {
return (
<div className='commmentBox'>
<h1>Comments</h1>
<CommentList data={this.state.data} />
<br />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
},
handleCommentSubmit: function(comment) {
commentsData.push(comment);
this.loadComments();
},
loadComments: function() {
console.log(commentsData.length);
this.setState({data: commentsData});
}
});
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return <Comment author={comment.author} text={comment.text} />;
});
return (
<div className='commentList'>
{commentNodes}
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<form className='commentForm' onSubmit='handleSubmit'>
<input type='text' placeholder='Your name' ref='author'/>
<input type='text' placeholder='Your comment' ref='text'/>
<input type='submit' value='Post' />
</form>
);
},
handleSubmit: function() {
var author = this.refs.author.getDOMNode().value.trim();
var text = this.refs.text.getDOMNode().value.trim();
this.props.onCommentSubmit({author: author, text: text});
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return false;
}
});
var Comment = React.createClass({
render: function() {
return(
<div className='comment'>
<br />
<h3 className='commentAuthor'>
{this.props.author} wrote:
</h3>
<h3 className='commentText'>
{this.props.text}
</h3>
</div>
);
}
});
React.renderComponent(
<CommmentBox />,
document.getElementById('content')
);
</script>
</body>
</html>
When I add comments, they don't show up in the comments list. I'm logging the length of the comment array to the console, and it never changes. What am I doing wrong?
You need to do this because you were using a string in onSubmit event.
<form className='commentForm' onSubmit={this.handleSubmit}>
You had this in your sample code:
<form className='commentForm' onSubmit='handleSubmit'>
Your code caused a Uncaught TypeError: string is not a function error. Because of that error it was not hitting the handleSubmit function and also caused the browser to reload.