I wrote the following code to use axios inside getInitialState:
var Player = createReactClass({
readDataFromServer: function() {
return axios.get("**** url address ****")
.then(res => {
return {name: res[name]}
});
},
render: function() {
return this.readDataFromServer().then((res) => {
return (<input value={res.name} type="text"/>)
}
}
}
I'd expect that I'll get an input with text inside. 'render' function returns a only when the promise is resolved, and then it returns
But, still, I just get an error saying:
Player.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
By the way, if I change readDataFromServer function to:
readDataFromServer: function() {
return {name: 'John'}
}
I get an input box with the text 'John' inside.
You would probably have to use react-redux for these async axios calls. When you initiate the app, your getInitialState function doesn't have a return value, so when it first renders it to the DOM, it has an empty value. If you are to console.log the returned value, you will probably see it in the console, however, since the value was null when you first render it, it does not display the returned value. Look into react-redux, where it manages your datastore, once you get the returned value from the axios call, update the redux value, and render the redux value in your
render: function() {
return (<input value={this.redux.name} type="text"/>)
}
call instead, and it will update the DOM automatically once it receives the response form the server.
Feel free to look at the boilerplate for React and Express application as an example: https://github.com/rjzheng/REWBoilerplate.git. Look inside the /app folder and see how reducers are set up.
Related
I use angularjs and I have a problem with ng-if when I use a function that returns true or false in two API, the browser is freezes
self.isShow= function() {
service.getKioskLogByKioskId([stateParams.kioskId], function (data) {
self.kioskLog = data;
service.getKiosk([stateParams.kioskId], function (kiosk) {
self.kiosk = kiosk;
debugger
if (self.kioskLog.isConnected && self.kiosk.isActive)
return true;
else
return false;
});
});
}
and in html
ng-if="self.isShow()"
Angular's ng-if can't be an async, it expects to get true/false in synchronous manner.
Don't forget that EVERY angular directive creates a "watch" that will be invoked as part of angular's dirty check mechanism, If you make a "heavy" operation on it, it will stuck the browser.
Basically there are 2 wrong things in your code, the first one, your isShow is not returning boolean value at all, (it always returns undefined).
The second one, you are probably making an API call inside the service.getKioskLogByKioskId method.
In order to solve both of the issues, you can make the service.getKioskLogByKioskId call inside the constructor of you controller, (if it is a component there is $onInit lifecycle hook for that), then save the async result on the controller, and use it the view.
It should look something like that:
class MyController {
constructor(stateParams) {
this.stateParams = stateParams;
this.isShow = false; // initial value
}
$onInit() {
const self =this;
service.getKiosk([stateParams.kioskId], function (kiosk) {
self.kiosk = kiosk;
debugger
if (self.kioskLog.isConnected && self.kiosk.isActive)
self.isShow = true;
else
self.isShow = false;
});
}
}
// view.html
<div ng-if="$ctrl.isShow"></div>
I've been developing in React for a while for my work, but recently I was requested to get some applications to ~100% test coverage using Istanbul. I've wrote over 160 tests for this application alone in the past few days, but I haven't been able to cover certain parts of my code. Im having the most trouble covering AJAX calls, setTimeout callbacks, and component methods that require another component to operate properly.
I've read several SO questions to no avail, and I believe that is because I'm approaching this incorrectly. I am using Enzyme, Chai assertions, Mocha, Istanbul coverage, sinon for spies, and was considering nock since I cant get sinon fakeServer working.
Here is the component method in question:
_getCategoriesFromServer() {
const _this = this;
sdk.getJSON(_this.props.sdkPath, {
itemsperpage: 10000
}).done(function(data) {
_this.setState({
isLoaded: true,
categories: _this.state.categories.concat(data)
});
});
}
Here is the test for that component:
it('should call _getCategoriesFromServer', () => {
sinon.spy(CategoryTree.prototype, '_getCategoriesFromServer');
wrapper = mount(<CategoryTree {...props} />);
expect(CategoryTree.prototype._getCategoriesFromServer.calledOnce).to.be.true;
});
The sdk is just a module that constructs a jQuery API call using getJSON.
My test is covering the function call, but its not covering the .done callback seen here:
So my question is, how can I properly test the .done?
If anyone has an article, tutorial, video, anything that explains how to properly test component methods, I would really appreciate it!
Second question is, how can I go about testing a method that gets passed down as a prop to a child component? With the testing coverage requirement I have to have that method tested, but its only purpose is to get passed down to a child component to be used as an onClick. Which is fine, but that onClick is dependent on another AJAX call returning data IN the child component.
My initial impulse was to just use enzymes .find to locate that onClick and simulate a click event, but the element with that onClick isn't there because the AJAX call didn't bring back data in the testing environment.
If you've read this far, I salute you. And if you can help, I thank you!
You could use rewire(https://github.com/jhnns/rewire) to test your component like this:
// let's said your component is ListBox.js
var rewire = require("rewire");
var myComponent = rewire("../components/ListBox.js");
const onDone = sinon.spy()
const sdkMock = {
getJSON (uri, data) {
return this.call('get', uri, data);
},
call: function (method, uri, data) {
return { done: function(){ onDone() } }
}
};
myComponent.__set__("sdk", sdkMock);
and finally you will test if the done function get called like this:
expect(onDone.calledOnce)to.be.true
With this should work as expected. If you need more options you could see all the options of rewire in GitHub.
BABEL
If you are using babel as transpiler you need to use babel-plugin-rewire(https://github.com/speedskater/babel-plugin-rewire) you could use it like this:
sdk.js
function call(method, uri, data) {
return fetch(method, uri, data);
}
export function getJSON(uri, data) {
return this.call('get', uri, data);
}
yourTest.js
import { getJSON, __RewireAPI__ as sdkMockAPI } from 'sdk.js';
describe('api call mocking', function() {
it('should use the mocked api function', function(done) {
const onDone = sinon.spy()
sdkMockAPI.__Rewire__('call', function() {
return { done: function(){ onDone() } }
});
getJSON('../dummy.json',{ data: 'dummy data'}).done()
expect(onDone.calledOnce)to.be.true
sdkMockAPI.__ResetDependency__('call')
})
})
I get my initial data from an outside JSON in
componentDidMount: function() { .... $.get(jsonfile, function(data) { ....
But the state of "jsonfile" changes via an input.
When the state of "jsonfile" prop changes the render method is invoked again, but I will also want to re-run the $.get request.
What would be the proper (react) way to do it?
You should abstract away your data fetching. If you put your fetching of data in a separate helper method you can call that method when needed, and it should do the fetching (and later updating) of the state
React.createClass({
componentDidMount: function () {
this.fetchData();
},
fetchData: function () {
var _this = this;
$.get('....', function (result) {
_this.setState(result);
});
},
handleClick: function () {
this.fetchData();
},
render: function () {
return (<div onClick={this.handleClick}>{data}</div>);
},
});
Please do upload some code from your project if you can.
From what I understand, you are calling an API to produce a JSON response and you have a user input to the same json?
In that case if you want to make supplementary calls to the API, you should place your API call in the correct Lifecycle Methods provided by a react component.
Please see https://facebook.github.io/react/docs/component-specs.html
I'm trying to get the new Firebase queries working in my factory, but I'm stuck. I want to return the value of 'logo' out of my opponents table, based on the opponent name. I can get the example with console.log from the Firebase docs running:
function OpponentService($firebase) {
var factory = {};
var _url = 'https://somefirebaseproject.firebaseio.com/opponents';
var _ref = new Firebase(_url);
factory.getLogo = function(opponent) {
_ref.orderByChild('name').equalTo(opponent).on("child_added", function(snapshot){
console.log(snapshot.val().logo);
});
};
return factory;
}
When I call my factory with OpponentService.getLogo(opponentName) in my controller, I get the correct logo id in my console. But how can I return the value instead of sending it to my console? I want to store it in a variable like this: $scope.opponentLogo = OpponentService.getLogo(opponentName). I tried several variations with a return statement like this:
factory.getLogo = function(opponent) {
_ref.orderByChild('name').equalTo(opponent).on("child_added", function(snapshot){
return snapshot.val().logo;
});
};
But apparently I don't fully understand how factories work in Angular, because I get an undefined. Could it be that the value isn't available yet and I should use a promise in someway? Anyone who can point me in the right direction?
You're returning the value of logo from the anonymous function inside the Firebase on() call, but you're not returning anything from getLogo().
Returning a promise would be a good way to do this. This is how you retrieve the opponent logo with AngularFire, if there is no guarantee opponentName will be unique:
// getLogo() returns a promise that you can bind to the UI.
// When loading finishes, the binding will get the opponent's logo value.
factory.getLogo = function (opponentName) {
var opponentsArray = $firebase(_ref.orderByChild('name').equalTo(opponentName)).$asArray();
// $loaded() returns a promise, so we can use the then() method to add
// functionality when the array finishes loading and the promise resolves.
var r = opponentsArray.$loaded().then(function () {
// Now return the logo for the first matching opponent
return opponentsArray[0].logo;
});
return r;
};
If opponentName is unique, I would rethink the data structure so that you could use the opponentName as the key for opponents. Then you would be guaranteed to get a single opponent and could fetch it with:
var opponent = $firebase(_ref.child(opponentName)).$asObject();
If you're not using AngularFire, you can return a promise using Angular's $q service. The following should work:
factory.getLogo = function(opponent) {
$q(function (resolve, reject) {
function successCallback(snapshot) {
resolve(snapshot.val().logo);
};
function cancelCallback(error) {
reject(error); // pass along the error object
};
_ref.orderByChild('name').equalTo(opponent)
.on("child_added", successCallback, cancelCallback);
});
};
You can assign the result of getLogo() to a scope variable, and bindings will update in the UI when Firebase returns the value.
I am attempting to write a directive to resolve promises and add data to the scope so that I can lazily load data into child elements from an API.
Through console.logging I am certain that I am resolving the promise and getting my expected data, but nothing updates on the page. I am assuming that dynamically adding to the scope from inside a directive is causing my problem.
The directive seems fairly trivial and I know that the promise is getting resolved:
app.directive('resolver', () => {
return {
restrict: "AEC",
scope: {
outName: "#",
promise: "&",
defaultVal: "="
},
link: function ($scope, element, attrs) {
var promise = $scope.promise,
outName = $scope.outName || 'myPromise',
defaultVal = $scope.defaultVal || {};
$scope[outName] = defaultVal;
promise().then(data => {
$scope[outName] = data;
});
}
}
});
I was hoping that this would let me do something like the following:
<li class="company-entry" ng-repeat="company in currentFeed.companies" resolver promise="getPreview(company.companyId)" out-name="profile" default-val="{name:'Loading...',description:'',images:[],sources:[]}">
...
<a ui-sref="company.feed({ id: company.companyId })" class="company-name">{{ profile.name || 'foo' }}</a>
</li>
Firstly, I have profile.name || 'foo' in order to test if the default-val part of my code would work; since it stays at foo, not even the default value gets placed into profile.
Secondly, after the promise is resolved, I should be setting profile to the data, but the page does not updating.
Adding a $scope.$apply() causes me to get rootscope update already in progress errors.
Is there any way to accomplish what I am trying to do?
I would solve this differently. Instead of building this into a directive you can easily produce a thenable that automatically unwraps just like ngResource does.
Let's consider an example: You have an array you need to fetch online with a getArray() function which returns a promise and makes an http request.
Normally you do:
getArray().then(function(result){
$scope.data = result;
});
Which I assume you're trying to avoid because it's a lot to write if everything in your scope is like this. The trick promises used to use and ngResource still uses in Angular is the following:
Return an empty result, which is also a promise
Whenever that promise resolves, update the result on the screen.
For example, our getArray() could look something like:
function getArray(){
var request = $http(...); // actual API request
var result = []
result.then = request.then; // make the result a promise too
result.then(function(resp){
// update _the same_ object, since objects are passed by reference
// this will also update every place we've send the object to
resp.data.forEach(function(el){ result.push(el); });
});
return result;
}
Now if you call getArray() and assign the result to a scope variable, whenever the promise resolves it will update the value.