I'm trying to build a basic chat app using React and Socket.Io based on the React tutorial https://facebook.github.io/react/docs/tutorial.html but keep getting an error "Cannot read property 'emit' of undefined". It's probably something trivial that I overlooked but I can't figure it out.
The function that triggers the error is:
handleSubmit: function (e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({ author: author, text: text });
this.setState({ author: '', text: '' });
this.socket.emit('message', comment);
},
The full code is
import React, { Component, PropTypes } from 'react';
import ReactDom from 'react-dom';
import io from 'socket.io-client'
/********************************************************************************************************/
var CommentBox = React.createClass({
getInitialState: function () {
return { data: [] };
},
handleCommentSubmit: function (comment) {
this.setState({ data: [comment, ...this.state.data] });
},
componentDidMount: function (data) {
this.socket = io.connect();
this.socket.on('message', function (comment) {
this.setState({ data: [comment, ...this.state.data] });
});
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</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({
getInitialState: function () {
return { author: '', text: '' };
},
handleAuthorChange: function (e) {
this.setState({ author: e.target.value });
},
handleTextChange: function (e) {
this.setState({ text: e.target.value });
},
handleSubmit: function (e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({ author: author, text: text });
this.setState({ author: '', text: '' });
this.socket.emit('message', comment);
},
render: function () {
return (
<div>
<form className='commentForm' onSubmit={this.handleSubmit}>
<input type='text' placeholder='Name' value={this.state.author} onChange={this.handleAuthorChange} />
<input type='text' placeholder='Enter Message' value={this.state.text} onChange={this.handleTextChange} />
<input type='submit' value='Post' />
</form>
</div>
);
}
});
/********************************************************************************************************/
var Comment = React.createClass({
render: function () {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
/********************************************************************************************************/
ReactDom.render(
<CommentBox />,
document.getElementById('container')
);
The call to this.socket.emit('message', comment) is at the wrong place neither this.socket nor comment is defined in your CommentForm Component.
You have to call this.socket.emit('message', comment) in the handleCommentSubmit Method in the CommentBox Component. (Line 14 in the second code example)
Related
I'm not sure why i'm getting this error, i've modified the code from the tutorial so that this.setState({data: data}); becomes this.setState({data: data.datalist});to reflect the response from the backend. I've made the changes to my code according to this answer but the same error persists React JS - Uncaught TypeError: this.props.data.map is not a function.
Sample code from tutorial configured to fetch JSON from my backend:
import React from 'react'
import ReactDOM from 'react-dom'
import $ from 'jquery'
var Comment = React.createClass({
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.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>
);
}
});
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({cache:false});
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
console.log(data.datalist);
this.setState({data: data.datalist});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</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({
getInitialState: function() {
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
});
ReactDOM.render(
<CommentBox url="/core/get_json" pollInterval={2000} />,
document.getElementById('app')
);
JSON from backend(django):
def get_json(request):
return JsonResponse({'datalist': [
{'id': 1, 'author': "Pete Hunt", 'text': "This is one comment"},
{'id': 2, 'author': "Jordan Walke", 'text': "This is *another* comment"}
]})
Browser getting said JSON under network response:
{"datalist": [{"id": 1, "author": "Pete Hunt", "text": "This is one comment"}, {"id": 2, "author": "Jordan Walke", "text": "This is *another* comment"}]}
Render calls this.props.data right away, your data might not have arrive yet, so you need to check if data is ready, by doing this this.props.data && this.props.data.map.
var commentNodes = this.props.data && this.props.data.map(function(comment)
{
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
map function is being called two times, once before the request and one time after. To correct this you can use
{this.props.data.length > 0 && this.props.data.map()}
This would do the trick.
I am trying to implement a component which handles ajax requests to the server and passes data to two child components. However it appears the ajax calls are never being made.
Feed.js:237 Uncaught TypeError: Cannot read property 'data' of null
is the error I'm getting.
// Parent component containting feed and friends components,
// handles retrieving data for both
import React, { Component } from 'react'
import FeedBox from './Feed.js'
import FriendsBox from './Friends'
const config = require('../../config')
export default class FrontPage extends Component {
constructor(props) {
super(props)
this.state = {
data: {},
pollInterval: config.pollInterval
}
this.loadDataFromServer = this.loadDataFromServer.bind(this)
}
loadDataFromServer() {
$ajax({
url: config.apiUrl + 'frontpage',
dataType: 'jsonp',
cache: false,
success: (data) => {
this.setState({data: data})
console.log(data)
},
error: (xhr, status, err) => {
console.error(this.url, status, error.toString())
}
})
}
componentDidMount() {
this.loadDataFromServer()
setInterval(this.loadDataFromServer, this.state.pollInterval)
}
componentWillUnmount() {
this.state.pollInterval = false
}
render() {
return (
<div className="FrontPage">
<FeedBox data={ this.state.data.feedData } />
<FriendsBox data={ this.state.data.friendsData } />
</div>
)
}
}
EDIT:
Here is the code for the Feed component, one of the two child components of FrontPage which is being passed props from its parent
import React, { Component } from 'react';
var config = require('../../config')
class Thread extends Component {
rawMarkup() {
var rawMarkup = marked(this.props.children.toString(), { sanitize: true })
return {__html: rawMarkup }
}
render() {
return (
<div className="thread">
<h4 className="threadVictim">Dear {this.props.victim}: </h4>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
<p>signed,</p>
<div>{this.props.author} and {this.props.ct} others.</div>
<hr></hr>
</div>
)
}
}
class ThreadList extends Component {
render() {
var threadNodes = this.props.data.map(function (thread) {
return (
<Thread victim={ thread.victim } author={ thread.author } ct={ thread.included.length } key={ thread._id }>
{ thread.text }
</Thread>
)
})
return (
<div className="threadList">
{ threadNodes }
</div>
)
}
}
var ThreadForm = React.createClass({
getInitialState: function () {
return {author: '',
text: '',
included: '',
victim: '',
ct: ''
}
},
handleAuthorChange: function (e) {
this.setState({author: e.target.value})
},
handleTextChange: function (e) {
this.setState({text: e.target.value})
},
handleIncludedChange: function (e) {
this.setState({included: e.target.value})
},
handleVictimChange: function (e) {
this.setState({victim: e.target.value})
},
handleSubmit: function (e) {
e.preventDefault()
var author = this.state.author.trim()
var text = this.state.text.trim()
var included = this.state.included.trim()
var victim = this.state.victim.trim()
if (!text || !author || !included || !victim) {
return
}
this.props.onThreadSubmit({author: author,
text: text,
included: included,
victim: victim
})
this.setState({author: '',
text: '',
included: '',
victim: '',
})
},
render: function () {
return (
<form className="threadForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange} />
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange} />
<input
type="text"
placeholder="Name your victim"
value={this.state.victim}
onChange={this.handleVictimChange} />
<input
type="text"
placeholder="Who can see?"
value={this.state.included}
onChange={this.handleIncludedChange} />
<input type="submit" value="Post" />
</form>
)
}
})
var ThreadsBox = React.createClass({
// loadThreadsFromServer: function () {
// $.ajax({
// url: config.apiUrl + 'feed',
// dataType: 'jsonp',
// cache: false,
// success: function (data) {
// this.setState({data: data})
// }.bind(this),
// error: function (xhr, status, err) {
// console.error(this.url, status, err.toString())
// }.bind(this)
// })
// },
handleThreadSubmit: function (thread) {
var threads = this.state.data
var newThreads = threads.concat([thread])
this.setState({data: newThreads})
$.ajax({
url: config.apiUrl + 'threads',
dataType: 'json',
type: 'POST',
data: thread,
success: function (data) {
this.setState({data: data})
}.bind(this),
error: function (xhr, status, err) {
this.setState({data: threads})
console.error(this.url, status, err.toString())
}.bind(this)
})
},
// getInitialState: function () {
// return {data: [],
// pollInterval: config.pollInterval}
// },
// componentDidMount: function () {
// this.loadThreadsFromServer()
// setInterval(this.loadThreadsFromServer, this.state.pollInterval)
// },
// componentWillUnmount: function () {
// this.state.pollInterval = false;
// },
render: function () {
return (
<div className="threadsBox">
<div className="feedNav">
<h1>Home</h1>
<h1>Heat</h1>
</div>
<ThreadList data={ this.state.data } />
<ThreadForm onThreadSubmit={ this.handleThreadSubmit } />
</div>
)
}
})
module.exports = ThreadsBox
You need to define the initial state object in ThreadsBox so it is not null:
var ThreadsBox = React.createClass({
getInitialState: function() {
return {
data: {}
};
}
// ...
})
module.exports = ThreadsBox
the ajax call is not firing. try adding the method for the request: GET or POST.
I have a (reasonably complex) form in react that in a pared-down form looks like this:
var MyForm = React.createClass({
init: { data: { "MyValue" : "initial value" } },
getInitialState: function() {
return this.init;
},
componentDidMount: function () {
this.setState({data: this.props.basedata });
},
save: function (ev) {
ev.preventDefault();
var d = this.state.data;
console.log(ev, d, d.MyValue)
},
render: function () {
if(this.state.data === null){ return; }
var d = this.state.data;
return (
<div>
<form>
<input type="text" placeholder="Enter Value" defaultValue={d.MyValue} />
<button onClick={this.save}>Save</button>
</form>
<div>{d.MyValue}</div>
</div>);
}
});
var dataObject = { "MyValue" : "external value" }
ReactDOM.render( <MyForm basedata={dataObject} />, document.getElementById('container'));
(available as a jsfiddle here: https://jsfiddle.net/achesser/d8veuvnh/1/)
Does react have a "built in" way of ensuring that the value of d.MyValue is updated to the value within the input box at the time I click "save"? or do I have to write an OnChange handler for each input?
I've update the jsfiddle , I bound {d.MyValue} to the html value property in stead of the defaultValue:
var Hello = React.createClass({
init: { data: { "MyValue" : "initial value" } },
getInitialState: function() {
return this.init;
},
componentDidMount: function () {
this.setState({data: this.props.basedata });
},
save: function (ev) {
ev.preventDefault();
var d = this.state.data;
console.log(ev, d, d.MyValue)
},
render: function () {
if(this.state.data === null){ return; }
var d = this.state.data;
return (
<div>
<form>
<input type="text" placeholder="Enter Value" value={d.MyValue} />
<button onClick={this.save}>Save</button>
</form>
<div>{d.MyValue}</div>
</div>);
}
});
var dataObject = { "MyValue" : "external value" }
ReactDOM.render( <Hello basedata={dataObject} />, document.getElementById('container'));
I'm just digging into Reactjs. In my crude example I would like the delete button to remove an item from the items array. How would I do this from handleClick in the Btn component? Or what is the best way to handle this?
var data= {
"items": [
{
"title": "item1"
},
{
"title": "item2"
},
{
"title": "item3"
}
]
};
var Btn = React.createClass({
handleClick: function(e) {
console.log('clicked delete'+ this.props.id);
//how does delete button modify this.state.items in TodoApp?
},
render: function() {
return <button onClick={this.handleClick}>Delete</button>;
}
});
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText, index) {
return <li key={index + itemText}>{itemText} <Btn id={index} /></li>;
};
return <div><ul>{this.props.items.map(createItem)}</ul></div>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
console.log("getInitialState");
return data;
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, document.getElementById('container'));
JSFiddle example
The Flux way would be to update your data store and trigger a render of your app, f.ex:
var deleter = function(id) {
data.items.splice(id,1)
React.render(<TodoApp />,
document.getElementById('container'));
}
[...]
handleClick: function(e) {
deleter(this.props.id)
}
Demo: http://jsfiddle.net/s01f1a4a/
By sending a callback to your Btn component. Explained further in this answer.
If you go here: http://facebook.github.io/react/index.html you will find tutorials in jsx but how do I use only js?
/** #jsx React.DOM */
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.renderComponent(<TodoApp />, mountNode);
For example the above code is using a listener, how do I do that in plain javascript?
thanks
Just found this: http://facebook.github.io/react/jsx-compiler.html
/** #jsx React.DOM */
var TodoList = React.createClass({displayName: 'TodoList',
render: function() {
var createItem = function(itemText) {
return React.DOM.li(null, itemText);
};
return React.DOM.ul(null, this.props.items.map(createItem));
}
});
var TodoApp = React.createClass({displayName: 'TodoApp',
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
React.DOM.div(null,
React.DOM.h3(null, "TODO"),
TodoList( {items:this.state.items} ),
React.DOM.form( {onSubmit:this.handleSubmit},
React.DOM.input( {onChange:this.onChange, value:this.state.text} ),
React.DOM.button(null, 'Add #' + (this.state.items.length + 1))
)
)
);
}
});
React.renderComponent(TodoApp(null ), mountNode);