I'm trying to get a simple "drawer" component working to test React's TransitionGroups. What I have so far below and on JSBin. If you try to run it, it works but I'm getting the error:
Uncaught TypeError: Cannot read property 'componentWillLeave' of undefined
What am I doing wrong here?
var DrawerInner = React.createClass({
componentWillEnter: function(cb) {
var $el = $(this.getDOMNode());
var height = $el[0].scrollHeight;
$el.stop(true).height(0).animate({height:height}, 200, cb);
},
componentWillLeave: function(cb) {
var $el = $(this.getDOMNode());
$el.stop(true).animate({height:0}, 200, cb);
},
render: function() {
return <div className="drawer" ref="drawer">{this.props.children}</div>;
}
});
var Drawer = React.createClass({
getInitialState: function() {
return {
open: false
};
},
componentWillMount: function() {
this.setState({
open: this.props.open
});
},
componentWillReceiveProps: function(props) {
this.setState({
open: props.open
});
},
open: function() {
this.setState({
open: true
});
},
close: function() {
this.setState({
open: false
});
},
toggle: function() {
this.setState({
open: !this.state.open
});
},
render: function() {
return (
<ReactTransitionGroup transitionName="test" component={React.DOM.div}>
{this.state.open && <DrawerInner key="content">{this.props.children}</DrawerInner>}
</ReactTransitionGroup>
);
}
});
Looks like this is a bug in ReactTransitionGroup. I've just put up a fix ReactTransitionGroup: Fix moving from falsey child.
Related
I am making a get request to a quiz api. When the user gets the answer correct the next answer is shown.
This is all working well, however I have got into some trouble when trying to clear the input box when the user gets the answer correct. I read this earlier and as far as I can tell it should be following the same logic.
Can anyone spot what is wrong here?
var Quiz = React.createClass({
getInitialState: function() {
return {
question: '',
answer: '',
value: '',
score: 0
}
},
getData: function() {
$.get('http://jservice.io/api/random', function(data){
var response = data[0];
console.log(response)
this.setState({
question: response.question,
answer: response.answer
})
}.bind(this));
},
componentDidMount: function() {
this.serverRequest = this.getData()
},
checkAnswer: function(event) {
if(event.target.value.toLowerCase() === this.state.answer.toLowerCase()) {
this.setState({
score: this.state.score + 1,
value: ''
})
this.getData();
}
},
skipQuestion: function() {
this.getData();
},
render: function() {
var value = this.state.value
return (
<div>
<p>{this.state.question}</p>
<input type='text' value={value} onChange={this.checkAnswer}/>
<p onClick={this.skipQuestion}>New question</p>
<p>{this.state.score}</p>
</div>
)
}
});
I moved this code into a jsbin and your input clearing logic is working fine. However, as #finalfreq mentioned in your implementation it's impossible to type a full answer in to the input box, each input gets recognized but is never displayed. The fix for that is shown below. The only change is adding the else case in checkAnswer:
var Quiz = React.createClass({
getInitialState: function() {
return {
question: '',
answer: '',
value: '',
score: 0
}
},
getData: function() {
$.get('http://jservice.io/api/random', function(data){
var response = data[0];
console.log(response)
this.setState({
question: response.question,
answer: response.answer
})
}.bind(this));
},
componentDidMount: function() {
this.serverRequest = this.getData()
},
checkAnswer: function(event) {
if(event.target.value.toLowerCase() === this.state.answer.toLowerCase()) {
this.setState({
score: this.state.score + 1,
value: ''
})
this.getData();
} else {
this.setState({
value: event.target.value.toLowerCase()
})
}
},
skipQuestion: function() {
this.getData();
},
render: function() {
var value = this.state.value
return (
<div>
<p>{this.state.question}</p>
<input type='text' value={value} onChange={this.checkAnswer}/>
<p onClick={this.skipQuestion}>New question</p>
<p>{this.state.score}</p>
</div>
)
}
});
i'm learning Backbone.js from the online book 'backbone-fundamentals',
while trying the example i got an error in console related to underscore.js:
Uncaught TypeError: Cannot use 'in' operator to search for 'model' in undefined
i searched for the error on SO what i found as the cause of this error was that i should not pass strings to my model when initializing it. but i didn't.
here is my project, my model:
// models/todo.js
var app = app || {};
app.Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
toggle: function() {
this.save({
completed: !this.get('completed')
});
}
});
my collection:
// collections/todos.js
var app = app || {};
var TodoList = Backbone.Collection.extend({
model: app.Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
completed: function() {
return this.filter( function( todo ) {
return todo.get('completed');
});
},
remaining: function() {
return this.without.apply(this, this.completed());
},
nextOrder: function() {
if( !this.length ) {
return 1;
}
return this.last().get('order') + 1;
},
comperator: function(todo) {
return todo.get('order');
}
});
app.Todos = new TodoList();
i have two views.
// views/todos.js
var app = app || {};
app.TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template( $('#item-template').html() ),
events: {
'dbclick label': 'edit',
'keypress .edit': 'updateOnEnter',
'blur .edit': 'close'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html( this.template(this.model.attributes) );
this.$input = this.$('.edit');
return this;
},
edit: function() {
this.$el.addClass('editing');
this.$input.focus();
},
close: function() {
var value = this.$input.val().trim();
if( value ) {
this.model.save({ title: value });
}
this.$el.removeClass('editing');
},
updateOnEnter: function(e) {
if (e.which === ENTER_KEY) {
this.close();
}
}
});
and:
// views/app.js
// maybe i should rename this file to appView.js
var app = app || {};
app.AppView = Backbone.View.extend({
el: '.todoapp',
statsTemplate: _.template( $('#stats-template').html() ),
events: {
'keypress .new-todo': 'createOnEnter',
'click .clear-completed': 'clearCompleted',
'click .toggle-all': 'toggleAllComplete'
},
initialize: function() {
this.allCheckbox = this.$('.toggle-all')[0];
this.$input = this.$('.new-todo');
this.$footer = this.$('.footer');
this.$main = this.$('.main');
this.listenTo(app.Todos, 'add', this.addOne);
this.listenTo(app.Todos, 'reset', this.addAll);
this.listenTo(app.Todos, 'change:completed', this.filterOne);
this.listenTo(app.Todos, 'filter', this.filterAll);
this.listenTo(app.Todos, 'all', this.render);
app.Todos.fetch();
},
render: function() {
var completed = app.Todos.completed().length;
var remaining = app.Todos.remaining().length;
if(app.Todos.length) {
this.$main.show();
this.$footer.show();
this.$footer.html(this.statsTemplate({
completed: completed,
remaining: remaining
}));
this.$('#filters li a')
.removeClass('selected')
.filter('[href="#/' + (app.TodoFilter || '' ) + '"]')
.addClass('selected');
} else {
this.$main.hide();
this.$footer.hide();
}
this.allCheckbox.checked = !remaining;
},
addOne: function(todo) {
var view = new app.TodoView({ model:todo });
$('.todo-list').append( view.render().el );
},
addAll: function() {
this.$('.todo-list').html('');
app.Todos.each(this.addOne, this);
},
filterOne: function(todo) {
todo.trigger('visible');
},
filterAll: function() {
app.Todos.each(this.filterOne, this);
},
newAttributes: function() {
return {
title: this.$input.val().trim(),
order: app.Todos.nextOrder(),
completed: false
};
},
createOnEnter: function( event ) {
if( event.which !== ENTER_KEY || !this.$input.val().trim() ) {
return;
}
app.Todos.create( this.newAttributes() );
this.$input.val('');
},
clearCompleted: function() {
_.invoke(app.Todos.completed(), 'destroy');
return false;
},
toggleAllComplete: function() {
var completed = this.allCheckbox.checked;
app.Todos.each(function(todo) {
todo.save({
'completed': completed
});
});
}
});
app.js :
// js/app.js
var app = app || {};
var ENTER_KEY = 13;
var ESC_KEY = 27;
$(function() {
new app.AppView();
});
where should i look when such an error happens?
thank you for your time...
edit:
the error happens when i load the page without doing anything further.
the order of the files is:
<script src="js/models/todo.js"></script>
<script src="js/collections/todos.js"></script>
<script src="js/views/todos.js"></script>
<script src="js/views/app.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/app.js"></script>
the whole error message:
Uncaught TypeError: Cannot use 'in' operator to search for 'model' in undefined
(anonymous function) # underscore.js:5
j.each.j.forEach # underscore.js:5
j.pick # underscore.js:5
Backbone.View # backbone.js:1190
child # backbone.js:1852
(anonymous function) # app.js:9
n.Callbacks.j # jquery.js:2
n.Callbacks.k.fireWith # jquery.js:2
n.extend.ready # jquery.js:2
K # jquery.js:2
It is possible to declare 2 more functions in main function like this ?
var jquery4u = {
init: function() {
jquery4u.countdown.show();
},
countdown: function() {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
jquery4u.init();
and i receive the following error: Uncaught SyntaxError: Unexpected token ( on this line "show: function() {"
Remove the function from the right of the countdown (demo)
var jquery4u = {
init: function() {
jquery4u.countdown.show();
},
countdown: {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
jquery4u.init();
Next time, use jsFiddle to make a demo and click the "JSHint" button.
Actually, none of this will work. Unless you make countdown an object or you treat its sub-functions as proper functions.
Why: Under countdown, you created an instance of object not a function.
var jquery4u = {
countdown: function() {
show = function() {
console.log('show');
}
hide = function() {
console.log('hide');
}
jquery4u.countdown.show();
}
}
The above code is a valid code so it is possible. Unfortunately it will not return anything.
The proper way to do this is in this format:
var jquery4u = {
countdown: {
show: function() {
console.log('show');
},
hide: function() {
console.log('hide');
}
}
}
This will work. You can try it out by calling:
jquery4u.countdown.show();
I have the following code:
var Panel = React.createClass({
getInitialState: function () {
return {
user_id: null,
blogs: null,
error: false,
error_code: '',
error_code: ''
};
},
shouldComponentUpdate: function(nextProps, nextState) {
if (nextState.error !== this.state.error ||
nextState.blogs !== this.state.blogs ||
nextState.error_code !== this.state.error_code
) {
return true;
}
},
componentDidMount: function() {
var self = this;
var pollingInterval = setInterval(function() {
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}.bind(self)).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
}.bind(self));
}, 1000);
},
render: function() { ... }
});
The important part to focus on is the componentDidMount This will fetch every second, regardless if there is an error or not. The render function, assuming theres an error, will display the appropriate method. So for all intense and purpose, this code does exactly what I want it to do, it fetches, if it fails, it fetches again until it succeeds.
But I need to make some changes, and this is where I am lost. I want to say: Fetch once, pass or fail - it doesn't matter. THEN every 15 seconds after that initial fetch, try again - regardless of pass or fail
I would normally spin up a backbone collection and router along with a poll helper to do all this, but in this specific case there is no need for the extra overhead. So thats where I am stumped. How do I accomplish what I am trying to achieve?
You should be able to just refactor your code a bit to be able to call your polling function a few different ways (like manually for example and then at a specified interval):
componentDidMount: function() {
this.startPolling();
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
},
startPolling: function() {
var self = this;
setTimeout(function() {
if (!self.isMounted()) { return; } // abandon
self.poll(); // do it once and then start it up ...
self._timer = setInterval(self.poll.bind(self), 15000);
}, 1000);
},
poll: function() {
var self = this;
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
});
}
I am building a simple tab system with angularjs, but I'm having trouble referring to this in the parent function. I know I might be misunderstanding some fundamentals, so please educate me:
js:
$scope.tabs = {
_this: this, // doesn't work
open: function(elem) {
$scope.tabsOpen = true;
if(elem)
$scope[elem] = true;
},
close: function() {
$scope.tabsOpen = false;
},
about: {
open: function() {
$scope.aboutOpen = true;
_this.notification.close(); // doesn't work
$scope.tabs.notification.close(); // works
},
close: function() {
$scope.aboutOpen = false;
}
},
notification: {
open: function() {/*etc*/},
close: function() {/*etc*/}
},
message: {
open: function() {/*etc*/},
close: function() {/*etc*/}
},
}
How about:-
$scope.tabs = getTab();
function getTab(){
var tab = {
open: function(elem) {
$scope.tabsOpen = true;
if(elem)
$scope[elem] = true;
},
close: function() {
$scope.tabsOpen = false;
},
about: {
open: function() {
$scope.aboutOpen = true;
tab.notification.close(); // Should work
},
close: function() {
$scope.aboutOpen = false;
}
},
notification: {
open: function() {/*etc*/},
close: function() {/*etc*/}
},
message: {
open: function() {/*etc*/},
close: function() {/*etc*/}
},
}
return tab ;
}
This way you don't rely on what this context would be which is anyways determined by the execution context, not where it is defined. Here you are just using a local tab object created in the local scope while calling the function getTab and instead of doing _this.notification.close(); you can just do tab.notification.close();, where tab is really the this that you are looking for there. A simple Demo