I'm playing with ReactJs, and i have this simple code
var User = React.createClass({
render: function () {
return(
<li>
{this.props.email}
</li>
);
}
});
var UserList = React.createClass({
reload: function () {
var xhr = new XMLHttpRequest();
xhr.open('get', "http://localhost:64501/Home/GetUsers", true);
xhr.onload = function () {
var result = JSON.parse(xhr.responseText);
this.setState({ data: result });
console.log(JSON.stringify(result));
}.bind(this);
xhr.send();
},
getInitialState: function () {
return {
data: [
{ email: "bob#gmail.com", id: "1" },
{ email: "boby#gmail.com", id: "2" }
]
};
},
componentDidMount: function () {
window.setInterval(this.reload, 3000);
},
render: function () {
if (this.props.data != null) {
var userNodes = this.props.data.map(function (user) {
return (
<User email={user.email} key={user.id } ></User>
);
});
return (
<div>
<ul>{userNodes}</ul>
</div>
);
}
else {
console.log("this.props.data is null");
return null;
}
}
});
ReactDOM.render(<UserList />, document.getElementById('root'));
I got 2 issues :
1 - the datas returned by getInitialState function are not rendered by the component.
2 - setState does not refresh the component in the reload function.
You are reading this.props.data and you should be reading this.state.data.
Note the difference between component properties, which come externally, and component internal state.
Just replace all this.props.data with this.state.data and your code should work.
Related
I am trying to create two components, one that holds the results of an API call from iTunes. I want to be able to click on any one of the items and move it to the empty component, moveResults, and then move it back to searchResults if it is clicked again. From other exercises, I feel like I am close, however I keep getting an error about the this.handleEvent = this.handleEvent.bind(this). Any ideas as to where I might have gone wrong and some possible solutions?
var App = React.createClass({
getInitialState: function() {
return {
searchResults: [],
moveResults: []
}
},
this.handleEvent = this.handleEvent.bind(this);
showResults: function(response) {
this.setState({
searchResults: response.results,
moveResults: []
})
},
search: function(URL) {
$.ajax({
type: 'GET',
dataType: 'json',
url: URL,
success: function(response) {
this.showResults(response);
}.bind(this)
});
},
handleEvent(trackId) {
const isInSearchResults = this.state.searchResults.includes(trackId);
this.setState({
searchResults: isInSearchResults ? this.state.searchResults.filter(i => i !== trackId) : [...this.state.searchResults, trackId],
moveResults: isInSearchResults ? [...this.state.moveResults, trackId] : this.state.moveResults.filter(i => i !== trackId)
});
},
componentDidMount() {
this.search('https://itunes.apple.com/search?term=broods')
},
render: function(){
return (
<div>
<Results searchResults={this.state.searchResults} handleEvent={this.handleEvent}/>
<Results searchResults={this.state.moveResults} handleEvent={this.handleEvent} />
</div>
);
}
});
var Results = React.createClass({
render: function(){
let handleEvent = this.props.handleEvent;
var resultItems = this.props.searchResults.map(function(result) {
return <ResultItem key={result.trackId} trackName={result.trackName} onClick={() => handleEvent(resultItems.id)} />
});
return(
<ul>
{resultItems}
</ul>
);
}
});
var ResultItem = React.createClass({
render: function(){
return <li> {this.props.trackName} </li>;
}
});
ReactDOM.render(
<App />, document.getElementById('root')
);
I have got problem with my react component which renders form. Basically, when I enter countdown page, the form just doesn't work (by that I mean it doesnt act at all, I write for example 123 which is 2 min and 3 seconds and nothing happens, just nothing). But, for example, if I go on to main page and back to countdown page, it works. I have noticed that when entering this page the first time, componentWillMount works, but componentDidMount doesn't (it won't console.log the message).
Link to heroku: http://fathomless-lowlands-79063.herokuapp.com/?#/countdown?_k=mj1on6
CountdownForm.jsx
var React = require('react');
var CountdownForm = React.createClass({
onSubmit: function (e) {
e.preventDefault();
var strSeconds = this.refs.seconds.value;
if (strSeconds.match(/^[0-9]*$/)){
this.refs.seconds.value = '';
this.props.onSetCountdown(parseInt(strSeconds, 10));
}
},
render: function () {
return(
<div>
<form ref="form" onSubmit={this.onSubmit} className="countdown-form">
<input type="text" placeholder="Enter time in seconds" ref="seconds" />
<button className="button expanded">Start
</button>
</form>
</div>
);
}
});
module.exports = CountdownForm;
Countdown.jsx
var React = require('react');
var Clock = require('Clock');
var CountdownForm = require('CountdownForm');
var Controls = require('Controls');
var Countdown = React.createClass({
getInitialState: function () {
return {
count: 0,
countdownStatus: 'stopped'
};
},
componentDidUpdate: function (prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus)
{
switch (this.state.countdownStatus){
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count: 0})
case 'paused':
clearInterval(this.timer)
this.timer = undefined;
break;
}
}
},
componentDidMount: function() {
console.log("componentDidMount");
},
componentWillMount: function () {
console.log("componentWillMount");
},
componentWillUnmount: function () {
console.log('componentDidUnmount');
},
startTimer: function () {
this.timer = setInterval(() => {
var newCount = this.state.count - 1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
}, 1000);
},
handleSetCountdown: function (seconds){
this.setState({
count: seconds,
countdownStatus: 'started'
});
},
handleStatusChange: function (newStatus) {
this.setState({
countdownStatus: newStatus
});
},
render: function () {
var {count, countdownStatus} = this.state;
var renderControlArea = () => {
if (countdownStatus !== 'stopped') {
return <Controls countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange} />
} else {
return <CountdownForm onSetCountdown={this.handleSetCountdown} />
}
};
return(
<div>
<Clock totalSeconds={count} />
{renderControlArea()}
</div>
);
}
});
module.exports = Countdown;
I have solved the problem. Main issue was error: "Uncaught Error: Stateless function components cannot have refs.
at invariant" . The problem was with stateless components, so that's why I instead of using arrow functions in Main.jsx and Nav.jsx, I used the React.createClass({}).
I'm starting to learn react. There's an excellent example in the official docs about loading data initially via AJAX:
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
The code above gets the latest gist of a specific user from GitHub.
What is the best way in React to go about outputting a list of the last 10 gists of the specific user?
How would you modify the code sample above?
var UserGist = React.createClass({
getInitialState: function() {
return {
gists: []
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
this.setState({
gists: result
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return <div>
{this.state.gists.map(function(gist){
return <div key={gist.id}>{gist.owner.login}</div>
})}
<div>;
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
I am new to React (I'm used to working with Angular) and I am currently working on filtering my Todo list app based on a category selection.
I cloned the Todo list app from http://todomvc.com/examples/react/#/ . I added a 'category' input, which works, but now I am trying to filter by category once the list is shown.
I currently don't have any search function for categories and am looking for some guidance as to where to start. I'll post the code below but here is the link to my repo if you want to clone it: https://github.com/aenser/todo-react
app.jsx
var app = app || {};
(function () {
'use strict';
app.ALL_TODOS = 'all';
app.ACTIVE_TODOS = 'active';
app.COMPLETED_TODOS = 'completed';
var TodoFooter = app.TodoFooter;
var TodoItem = app.TodoItem;
var ENTER_KEY = 13;
var TodoApp = React.createClass({
getInitialState: function () {
return {
nowShowing: app.ALL_TODOS,
editing: null,
newTodo: '',
newCategory: ''
};
},
componentDidMount: function () {
var setState = this.setState;
var router = Router({
'/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
'/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
'/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
});
router.init('/');
},
handleChange: function (event) {
this.setState({newTodo: event.target.value});
},
handleCategoryChange: function (event) {
this.setState({newCategory: event.target.value});
},
handleNewTodoKeyDown: function (event) {
if (event.keyCode !== ENTER_KEY) {
return;
}
event.preventDefault();
var val = this.state.newTodo.trim();
var cat = this.state.newCategory.trim();
if (val, cat) {
this.props.model.addTodo(val, cat);
this.setState({newTodo: '', newCategory: ''});
}
},
toggleAll: function (event) {
var checked = event.target.checked;
this.props.model.toggleAll(checked);
},
toggle: function (todoToToggle) {
this.props.model.toggle(todoToToggle);
},
destroy: function (todo) {
this.props.model.destroy(todo);
},
edit: function (todo) {
this.setState({editing: todo.id});
},
save: function (todoToSave, text, cat) {
this.props.model.save(todoToSave, text, cat);
this.setState({editing: null});
},
cancel: function () {
this.setState({editing: null});
},
clearCompleted: function () {
this.props.model.clearCompleted();
},
render: function () {
var footer;
var main;
var todos = this.props.model.todos;
var shownTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) {
case app.ACTIVE_TODOS:
return !todo.completed;
case app.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={this.cancel}
/>
);
}, this);
var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);
var completedCount = todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
/>;
}
if (todos.length) {
main = (
<section className="main">
<input
className="toggle-all"
type="checkbox"
onChange={this.toggleAll}
checked={activeTodoCount === 0}
/>
<ul className="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<header className="header">
<h1>todos</h1>
<form onKeyDown={this.handleNewTodoKeyDown}>
<input
placeholder="What needs to be done?"
value={this.state.newTodo}
autoFocus={true}
className="new-todo"
onChange={this.handleChange}
/>
<select value={this.state.newCategory} className="new-todo"
onChange={this.handleCategoryChange}>
<option value="">Select a Category</option>
<option value="Urgent">Urgent</option>
<option value="Soon">Soon</option>
<option value="Anytime">Anytime</option>
</select>
</form>
</header>
{main}
{footer}
</div>
);
}
});
var model = new app.TodoModel('react-todos');
function render() {
React.render(
<TodoApp model={model}/>,
document.getElementsByClassName('todoapp')[0]
);
}
model.subscribe(render);
render();
})();
todoModel.js
var app = app || {};
(function () {
'use strict';
var Utils = app.Utils;
// Generic "model" object. You can use whatever
// framework you want. For this application it
// may not even be worth separating this logic
// out, but we do this to demonstrate one way to
// separate out parts of your application.
app.TodoModel = function (key) {
this.key = key;
this.todos = Utils.store(key);
this.onChanges = [];
};
app.TodoModel.prototype.subscribe = function (onChange) {
this.onChanges.push(onChange);
};
app.TodoModel.prototype.inform = function () {
Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); });
};
app.TodoModel.prototype.addTodo = function (title, category) {
this.todos = this.todos.concat({
id: Utils.uuid(),
title: title,
category: category,
completed: false
});
this.inform();
};
app.TodoModel.prototype.toggleAll = function (checked) {
// Note: it's usually better to use immutable data structures since they're
// easier to reason about and React works very well with them. That's why
// we use map() and filter() everywhere instead of mutating the array or
// todo items themselves.
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
});
this.inform();
};
app.TodoModel.prototype.filterAll = function () {
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo);
});
this.inform();
};
app.TodoModel.prototype.toggle = function (todoToToggle) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ?
todo :
Utils.extend({}, todo, {completed: !todo.completed});
});
this.inform();
};
app.TodoModel.prototype.destroy = function (todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate !== todo;
});
this.inform();
};
app.TodoModel.prototype.save = function (todoToSave, text, cat) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}, {category: cat});
});
this.inform();
};
app.TodoModel.prototype.clearCompleted = function () {
this.todos = this.todos.filter(function (todo) {
return !todo.completed;
});
this.inform();
};
})();
todoItem.jsx
var app = app || {};
(function () {
'use strict';
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
app.TodoItem = React.createClass({
handleSubmit: function (event) {
var val = this.state.editText.trim();
var cat = this.state.editCategoryText.trim();
if (val || cat) {
this.props.onSave(val, cat);
this.setState({editText: this.props.todo.title, editCategoryText: this.props.todo.category});
} else {
this.props.onDestroy();
}
},
handleEdit: function (event) {
this.props.onEdit();
this.setState({editText: this.props.todo.title, editCategoryText: this.props.todo.category});
},
handleKeyDown: function (event) {
if (event.which === ESCAPE_KEY) {
this.setState({editText: this.props.todo.title});
this.props.onCancel(event);
} else if (event.which === ENTER_KEY) {
this.handleSubmit(event);
}
},
handleChange: function (event) {
if (this.props.editing) {
this.setState({editText: event.target.value});
}
},
handleCategoryChange: function (event) {
if (this.props.editing) {
this.setState({editCategoryText: event.target.value});
}
},
getInitialState: function () {
return {editText: this.props.todo.title, editCategoryText: this.props.todo.category};
},
/**
* This is a completely optional performance enhancement that you can
* implement on any React component. If you were to delete this method
* the app would still work correctly (and still be very performant!), we
* just use it as an example of how little code it takes to get an order
* of magnitude performance improvement.
*/
shouldComponentUpdate: function (nextProps, nextState) {
return (
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText ||
nextState.editCategoryText !== this.state.editCategoryText
);
},
/**
* Safely manipulate the DOM after updating the state when invoking
* `this.props.onEdit()` in the `handleEdit` method above.
* For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
* and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
*/
componentDidUpdate: function (prevProps) {
if (!prevProps.editing && this.props.editing) {
var node = React.findDOMNode(this.refs.editField);
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}
},
render: function () {
return (
<li className={classNames({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.category}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
value={this.state.editText}
className="edit"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
<select value={this.state.EditCategoryText} className="edit" onChange={this.handleCategoryChange} defaultValue={this.props.todo.category} onKeyDown={this.handleKeyDown}>
<option value="Urgent">Urgent</option>
<option value="Soon">Soon</option>
<option value="Anytime">Anytime</option>
</select>
</li>
);
}
});
})();
Thank You for taking the time to help me figure out how to filter my search based on category selection.
Your interface is a little confusing as you seem to use the same input select for both assigning categories to todos and filtering, I'll get to that at the end of the answer, but for now, I just used the category selector for both entering data and filtering by category.
The answer to your question is extremely simple. You just filter by the category as well as by the completed state. Like this:
var shownTodos = todos.filter(function(todo) {
return(todo.category === this.state.newCategory);
}, this).filter(function (todo) {
switch (this.state.nowShowing) {
case app.ACTIVE_TODOS:
return !todo.completed;
case app.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);
I would add some more buttons along the bottom for the categorical currently on display. You would also add a new set of statuses like nowShowing for the category like nowShowingCategory. The buttons would set this to the 3 values of category, and you would use that variable in the above filter instead of newCategory from my example.
I am trying to update my table with a ajax call by using this.setState({ data: data }); but the datatable is not redrawn with the new data? (I can see that new data is received)
var GridRow = React.createClass({
render: function() {
var data = [], columns;
if(this.props.columns){
for(var i = 0; i < this.props.columns.length; i++){
data.push({
HTMLclass: this.props.columns[i].HTMLClass,
content: this.props.cells[i]
})
}
}
columns = data.map(function(col, i) {
return (
<td className={col.HTMLclass} key={i}>{col.content}</td>
);
}.bind(this));
return (
<tr>
{columns}
</tr>
);
}
});
var GridHead = React.createClass({
render: function() {
if(this.props.data){
var cell = this.props.data.Title;
var htmlClass = this.props.data.HTMLClass;
}
return (
<th className={htmlClass}>{cell}</th>
);
}
});
var GridList = React.createClass({
render: function() {
var tableClass = this.props.tableClass;
if(this.props.data){
var header = this.props.data.Columns.map(function (columns, i) {
return (
<GridHead data={columns} key={i} />
);
});
var row = this.props.data.Rows.map(function (row, i) {
return (
<GridRow columns={data1.Columns} cells={row.Cells} key={i} />
);
});
}
return (
<table className={tableClass}>
<tr>{header}</tr>
<tbody>
{row}
</tbody>
</table>
);
}
});
var GridPager = React.createClass({
handleClick: function(e) {
e.preventDefault();
this.props.onPaging();
},
render: function() {
return(
<a href='#' onClick={this.handleClick}>Paging</a>
);
}
});
var gridPage = 0;
var GridBox = React.createClass({
loadCommentsFromServer: function() {
var xhr = new XMLHttpRequest();
gridPage++;
xhr.open('get', this.props.url + '/?pageNr=' + gridPage, true);
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
handlePaginSubmit: function(comment) {
this.loadCommentsFromServer();
},
getInitialState: function() {
return { data: this.props.initialData };
},
render: function(){
var tableClass = this.props.tableClass;
return (
<div>
<GridList data={this.state.data} tableClass={tableClass} />
<GridPager onPaging={this.handlePaginSubmit} />
</div>
);
}
});
that = this
xhr.onload = function() {
var data = JSON.parse(xhr.responseText);
that.setState({ data: data });
}.bind(this);
In your code this is the context of the callback method where setState method is not available, but it's a callback and you don't get an error about it. Use the trick listed above so you have a reference to the right context.
setState() in react js ALWAYS causes rerender.
If your props are updating then better to use
componentWillReceiveProps(nextProps) {
this.props.data = nextProps.data;
}
react will render the component when state changes so you can use
this.setState(this.state)
If you want to update your render() method reads from something other than this.props or this.state, you'll need to tell React when it needs to re-run render() by calling
forceUpdate()