I am learning React. I am following a chapter in book where they load the data from API under componentDidMount and update the state of the component as
getInitialState: function () {
return {changeSets: []};
},
mapOpenLibraryDataToChangeSet : function (data) {
return data.map(function (change, index) {
return {
"when":jQuery.timeago(change.timestamp),
"who": change.author.key,
"description": change.comment
}
});
},
componentDidMount: function () {
$.ajax({
url: 'http://openlibrary.org/recentchanges.json?limit=10',
context: this,
dataType: 'json',
type: 'GET'
}).done(function (data) {
// console.log(data);
var changeSets = this.mapOpenLibraryDataToChangeSet(data);
console.log("changeSets:" + JSON.stringify(changeSets));
this.setState({changeSets: changeSets});
});
},
When I run this, I see error on console as
"TypeError: Cannot read property 'map' of undefined
at t.render (mikobuf.js:55:41)
at _renderValidatedComponentWithoutOwnerOrContext (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:17508)
at _renderValidatedComponent (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:17644)
at performInitialMount (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:13421)
at mountComponent (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:12467)
at Object.mountComponent (https://npmcdn.com/react#15.3.1/dist/react.min.js:15:2892)
at h.mountChildren (https://npmcdn.com/react#15.3.1/dist/react.min.js:14:26368)
at h._createInitialChildren (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:26619)
at h.mountComponent (https://npmcdn.com/react#15.3.1/dist/react.min.js:13:24771)
at Object.mountComponent (https://npmcdn.com/react#15.3.1/dist/react.min.js:15:2892)"
The running link is http://jsbin.com/mikobuf/edit?js,console,output
What am I doing wrong?
UPDATE
When I added the changeSets={data} while rendering the app, I see data in console
ReactDOM.render(<App headings = {headings} changeSets={data}/>, document.getElementById("container"))
But I want the data to be pulled from API. So as soon as I remove the changeSets={data} when rendering, it fails
ReactDOM.render(<App headings = {headings}/>, document.getElementById("container"))
You are trying to use the props changeSets when it is actually part of Apps state.
This:
<RecentChangesTable.Rows changeSets={this.props.changeSets} />
Should Be:
<RecentChangesTable.Rows changeSets={this.state.changeSets} />
http://jsbin.com/tuqeciyere/1/edit?js,console,output
Related
Running into a snag with trying to integrate my API with Vue/Axios. Basically, Axios is getting the data (it DOES console.log what I want)... But when I try to get that data to my empty variable (in the data object of my component) to store it, it throws an "undefined at eval" error. Any ideas on why this isn't working for me? Thanks!
<template>
<div class="wallet-container">
<h1 class="title">{{ title }}</h1>
<div class="row">
{{ thoughtWallet }}
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'ThoughtWallet',
data () {
return {
title: 'My ThoughtWallet',
thoughtWallet: [],
}
},
created: function() {
this.loadThoughtWallet();
},
methods: {
loadThoughtWallet: function() {
this.thoughtWallet[0] = 'Loading...',
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
this.thoughtWallet = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
}
}
}
</script>
Because you're using .then(function(..) { }) this won't refer to the vue context this.
You have two solutions, one is to set a variable that references the this you want before the axios call, e.g.:
var that = this.thoughtWallet
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
that = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
The other is to use the new syntax (for which you need to make sure your code is transpiled correctly for browsers that don't support it yet), which allows you to access this inside the scoped body of the axios then.
axios.get('http://localhost:3000/api/thoughts').then((response) => {
console.log(response.data); // DISPLAYS THE DATA I WANT
this.thoughtWallet = response.data; // THROWS TYPE ERROR: Cannot set property 'thoughtWallet' of undefined at eval
}).catch(function(error) {
console.log(error);
});
The reason this happens is because inside that function/then, this will be referring to the context of the function, hence there won't be a thoughtWallet property
this.thoughtWallet inside the .get method is referring to the axios object, not Vue's. You can simply define Vue's this on the start:
methods: {
loadThoughtWallet: function() {
let self = this;
this.thoughtWallet[0] = 'Loading...',
axios.get('http://localhost:3000/api/thoughts').then(function(response) {
console.log(response.data); // DISPLAYS THE DATA I WANT
self.thoughtWallet = response.data;
}).catch(function(error) {
console.log(error);
});
}
}
I'm trying to load a binary file et read the content
For this, i'm using the load function to get my binary file and then,
I call a function that parse the binary file.
The problem is , I can access the datas
I keep havin this error :
Uncaught (in promise) TypeError: Cannot read property 'parsePeturboDATFiles' of undefined
at eval (eval at 79 (0.05b4762….hot-update.js:7), :128:11)
I did try to console.log my data to see what is going wrong, but I can print my data but I can't pass it to my other parsing functions ... I cannot figure why.
Here's my code by the way :
<template>
<div class="cde">
<h1></h1>
</div>
</template>
<script>
import jbinary from 'jbinary'
export default {
name: 'CDE',
data () {
return {
}
},
methods : {
parsePeturboDATFiles : function (data) {
console.log(data)
},
},
mounted : function () {
jbinary.load('./static/test.dat').then(function (data) {
console.log(data.view) //works fine
this.parsePeturboDATFiles(data.view) //get an error
})
}
}
</script>
The error is saying that it's not able to read the parsePeturboDATFiles property because the this variable is evaluating to undefined. Store a reference to this in another variable self and then use that to call parsePeturboDATFiles():
mounted : function () {
var self = this;
jbinary.load('./static/test.dat').then(function (data) {
self.parsePeturboDATFiles(data.view);
})
}
I am following this tutorial on how to integrate react.js into rails and when I pre compile my assets I get an error with this code stating that (data) => this.setState({statuses: data}); causes SyntaxError: unknown: Unexpected token (14:44) I tried many things to get it to work and when I delete the semi-colon at the end it works except that the interval no longer gets called the console will say alert twice when the page is refreshed and then never again.
var StatusesContainer = React.createClass ({
componentWillMount() {
this.fetchStatuses();
setInterval(this.fetchStatuses(), 1000);
},
fetchStatuses() {
console.log("alert");
$.getJSON(
this.props.statusesPath,
{
space_id: this.props.space_id
},
(data) => this.setState({statuses: data});
);
},
getInitialState() {
return ({ statuses: [] });
},
render() {
return (<RecentStatuses statuses={ this.state.statuses }/>);
}
});
$.getJSON(
this.props.statusesPath,
{
space_id: this.props.space_id
},
(data) => this.setState({statuses: data});
);
Here is the problem: (data) => this.setState({statuses: data});, you should use , not ;
I've just figured out that object in React's state that have multiple children cannot be rendered easily.
In my example I have component which speaks with third-party API through AJAX:
var Component = React.createClass({
getInitialState: function () {
return {data: {}};
},
loadTrackData: function () {
api.getDataById(1566285, function (data) {
this.setState({data: data});
}.bind(this));
},
componentDidMount: function () {
this.loadTrackData();
},
render: function () {
return (
<div>
<h2>{this.state.data.metadata.title}</h2>
</div>
);
}
});
The problem is that {this.state.data.metadata} renders fine..
But {this.state.data.metadata.title} throws error Uncaught TypeError: Cannot read property 'title' of undefined!
What is the proper way to deal with such async data?
I always like to add the loading spinner or indicator if the page has async operation. I would do this
var Component = React.createClass({
getInitialState: function () {
return {data: null};
},
loadTrackData: function () {
api.getDataById(1566285, function (data) {
this.setState({data: data});
}.bind(this));
},
componentDidMount: function () {
this.loadTrackData();
},
render: function () {
var content = this.state.data ? <h2>{this.state.data.metadata.title}</h2> : <LoadingIndicator />;
return (
<div>
{content}
</div>
);
}
});
with the loading indicator basically it improve the user experience and won't get much of unwanted surprise. u can create your own loading indicator component with lots of choices here http://loading.io/
this.state.data.metadata is undefined until loading occurs. Accessing any property on undefined gives you a TypeError. This is not specific to React—it's just how JavaScript object references work.
I suggest you use { data: null } in initial state and return something else from render with a condition like if (!this.state.data).
I'm having a lot of trouble getting a simple jest test to work. Jest insists that my Ajax call is not happening, with the error message:
FAIL authTest.js (1.828s)
● Authentication: Logging In › it Doesn't currently have a logged in user
- Expected Function to be called with { url : 'api/loggedin', type : 'GET', error : <jasmine.any(function Function() { [native code] })>, success : <jasmine.any(function Function() { [native code] })> }.
at Spec.<anonymous> (/Users/ritmatter/reps/spec/authTest.js:13:20)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
The code being tested is in a file called auth.jsx, and it looks like this:
loggedIn: function() {
return $.ajax({
url: 'api/loggedin',
type: 'GET',
error: function(xhr, status, err) {
return false;
}.bind(this),
success: function(data) {
return true;
}.bind(this),
});
},
The test looks like this:
/** #jsx React.DOM */
"use strict";
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
describe('Authentication: Logging In', function() {
it('Doesn\'t currently have a logged in user', function () {
var $ = require('jquery');
jest.dontMock('../js/auth.jsx');
var auth = require('../js/auth.jsx');
auth.loggedIn();
expect($.ajax).toBeCalledWith({
url: 'api/loggedin',
type: 'GET',
error: jasmine.any(Function),
success: jasmine.any(Function)
});
});
});
Any idea why jest would think that this is not getting called? I've been looking around, and it seems like there are some bugs with respect to dontMock() and mock().
As Wagner mentioned, you need to require jquery globally, outside of your test. Your component is using the global version of $, so adding var $ = require('jquery') doesn't do anything in terms of adding jquery to the global variable $.
You also did not mock the ajax call.
When testing react, I avoid issues with loading jquery by simply redefining $:
window.$ = {ajax: jest.genMockFunction()}
So, as long as you don't need jquery for anything else other than an ajax call, this one line will simulate the jquery root and mock the ajax call.
I had a similar problem with a React component that invoked ajax on initialisation.
What I found is that expect on $ only work if you require jquery outside the it method.
My React component and test case are like these (They are ES6 but you can get the idea)
import React from 'react'
import $ from 'jquery'
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {things:[]};
}
componentWillMount() {
$.ajax({
url: 'http://localhost:3000/things',
success: (result) => this.setState(result),
error: (ex) => console.log(ex)
})
}
render() {
//stuff
}
}
and the test
jest.dontMock('../components/MyComponent')
import React from 'react'
import TestUtils from 'react-addons-test-utils'
import $ from 'jquery'
const Wall = require('../components/MyComponent');
describe('MyComponent', () => {
it('calls the things end point', () => {
const myComp = TestUtils.renderIntoDocument(<MyComponent />)
expect($.ajax).toBeCalledWith({
url: 'http://localhost:3000/things',
success: jasmine.any(Function),
error: jasmine.any(Function)
})
});
});