Due to the fact that I am new in meteor/react I can't figure out how to initialize my state variable.
My problem is that I would like to get
my mongo collection through the createContainer from react-meteor-data (as described here),
use the initialized prop to intialize the state variable
But the prop in the constructor is empty. Only when I the "gotClicked" function is called the prop.allLists is filled with the data from mongo.
Does anyone know why? My guess the data is loaded asynchronously, so that the data is not available yet in the constructor.
What would be a better way to get the data?
import React, {Component, PropTypes} from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import {AllLists} from '../api/alllists.js'
export default class MyList extends Component {
constructor(props) {
super();
console.log(props.allLists)
console.log(this.props.allLists)
//allLists is empty
this.state = {
lists: props.allLists
}
}
gotClicked(){
console.log(this.props.allLists)
//allLists is filled
}
render() {
return (
<div className="container" onClick={this.gotClicked.bind(this)}>
</div>
);
}
}
MyList.propTypes = {
allLists: PropTypes.string.isRequired
}
export default createContainer(() => {
return {
allLists: AllLists.find({}).fetch()
};
}, MyList);
You're right, the data is loaded asynchronously, and it might not be available in the constructor. However, the callback function you pass to createContainer is evaluated again when the data is loaded, and it automatically updates the props of your component.
To catch this change, implement the componentWillReceiveProps function in your React component.
componentWillReceiveProps(nextProps) {
this.setState({
lists: nextProps.allLists
});
}
Docs here: https://facebook.github.io/react/docs/component-specs.html
Related
I want to import an element and bind one of its properties. My import fails silently. I expect the value of userLocal to be an object. But, instead, userLocal is undefined.
my-app.html
<link rel="import" href="/src/app-state/state-user-local.html">
...
<state-user-local user-local="{{userLocal}}"></state-user-local>
...
<script>
/* #polymerElement */
class MyApp extends Polymer.Element {
static get is() { return 'my-app; }
static get properties() { return {
userLocal: {
type: Object,
notify: true,
},
...
}}
}
window.customElements.define(MyApp.is, MyApp);
</script>
with the following error message.
The element state-user-local is not defined
I know the import definition is correct because I am using VSCode and when I command+click the import it takes me to the correct file.
I know there is no problem with the <state-user-local> element itself because I successfully import it into other element/s in the app and obtain the expected value of userLocal there.
This problem sounds like what is described at the following links.
The element xxx is not defined (issue #54)
Recognize elements registered with MyElem.is (issue #540)
The first link discusses using "/* #polymerElement */ above the class" which is what I have tried (see above code) without success.
It seems to me that you didn't define the <state-user-local> element inside your file; you defined <my-app>. If you want to use the tag name <state-user-local> you need to define it as such.
<script>
class StateUserLocal extends Polymer.Element {
static get is() { return 'state-user-local'; }
static get properties() { return {
userLocal: {
type: Object,
notify: true,
},
...
}}
}
window.customElements.define(StateUserLocal.is, StateUserLocal);
</script>
Okay, so, I have a component with a function getPhotos(), which is taking in a string from parent props:
import React, { Component, PropTypes } from 'react';
// data
import { Cats } from '../api/collections/galleries.js';
export default class...
getPhotos() {
let gallery = this.props.galleryName; // gallery = "Cats"
Now, I want to use this string "Cats" to access a collection from mongo db, so I tried using window["Cats"], but I don't think it is on the window object:
let photoData = window[gallery].find().fetch(); // window[Cats] and window["Cats"] returns undefined
return photoData;
}
Everything in meteor seems to be working, publish and subscribe.
Any ideas how I can do this using React components?
You can do
Meteor.connection._stores[gallery]._getCollection().find().fetch();
I'm trying to write a React component which subscribes to data on the server by passing the collection name as a prop. (in the example the name is still hardcoded to keep things simple) Since the collection names can change dynamically, I'm calling a method to construct the publication and server collection variable. Now the theory is to construct the data publication, server and client mongo collection variable in a parent component and the subscribe to this publication in the child component. The parent component is set up like this:
export default class TilesSingle extends TrackerReact(Component) {
newDataCollection(){
Meteor.call("setupData","randomcollectionname");
if(!window["randomcollectionname"]){
window["randomcollectionname"] = new Meteor.Collection("randomcollectionname");
}
}
render(){
this.newDataCollection();
return(
<div>
<DataObject />
</div>
)
}
}
The setupData method looks like this:
Meteor.methods({
setupData(collectionname){
if(!global[collectionname]){
global[collectionname] = new Meteor.Collection(collectionname);
Meteor.publish("pub."+ collectionname,function() {
return global[collectionname].find();
});
}
}
})
And the child DataObject component looks like this:
export default class DataObject extends TrackerReact(Component) {
constructor(){
super();
this.state = {
subscription: {
tiledata: Meteor.subscribe("pub.randomcollectionname"),
}
}
}
componentWillUnmount(){
this.state.subscription.tiledata.stop();
}
findData(){
post = window['randomcollectionname'].findOne();
console.log(post)
return post;
}
render(){
obj = this.findData();
if(!obj){
obj = {};
obj["score"] = 0;
}
return(
<div>
<h4>{obj.score}</h4>
</div>
)
}
}
Notice that I'm logging post in the findData() function. When I refresh, the console logs:
undefined
undefined
> Object {..}
> Object {..}
undefined
undefined
It is almost as if the subscription works and then stops working. I'm thinking that it has to do with the order in which react renders these components, and that the method is not finished on the server by the time the client tries to render the child component. Any Ideas?
Today, I trying to make my own annotation to use it in Angular2 project.
This annotation must add body class on specific component.
So, I've search on Angular source code, but it's so difficult to see where and how annotation was created.
For the moment, I've tried this :
export function BodyClass(classes: any): ClassDecorator{
classes = classes || {};
if(typeof classes === 'string'){
classes = {classes};
}
return function changeBodyClass(){
console.log('ici');
}
}
And my component :
import {Component} from "angular2/core";
import {RouterOutlet} from "angular2/router";
import {BodyClass} from "../core/annotations/body_class";
#Component({
selector: 'my-component',
template: `
<router-outlet></router-outlet>
`,
})
#BodyClass('test')
export class MyComponent{
}
My console log in the annotation was correctly fired, but I want to use "DOM" class from angular2/src/platform/dom/dom_adapter to add my classes, but DOM in undefined when on console log it (not need to instanciate it).
However, the DOM class works well directly in my component.
I add classes in ngOnInit function, and remove them on ngOnDestroy.
But, I want this behavior on many component, and I think a new annotation is the best way.
Maybe you have a better idea for this ? Or to work with DOM class on a annotation ?
Thanks !
It's not so obvious since you want to work at the component instance level and not at the component class one. So you need to wrap the corresponding constructor function.
export function MyComponentDecorator(value: string) {
return function (target: Function) {
var original = target;
function construct(constructor, args) {
var c : any = function () {
// Call the target constructor
var ret = constructor.apply(this, args);
// Add your additional processing
return ret;
}
c.prototype = constructor.prototype;
return new c();
}
// The new constructor
// Don't forget to add a name at this function
// since Angular requires it for the template compilation
var f : any = function WrappedComponent(...args) {
return construct(original, args);
}
f.prototype = original.prototype;
return f;
}
}
At this point, you wrap the component instance but you lose the component metadata. You need to copy them by hand:
f.prototype = original.prototype;
var annotations = Reflect.getMetadata('annotations', original));
Reflect.defineMetadata('annotations', annotations, f);
var properties = Reflect.getMetadata('propMetadata', original));
Reflect.defineMetadata('propMetadata', properties, f);
return f;
To use this decorator simply add it before or after the #Component one:
#MyComponentDecorator()
#Component({
selector: 'sub',
template: `
<div (click)="showMessage()">Test</div>
`
})
export class SubComponent {
(...)
}
You can notice that this decorator only copies the metadata at the component level and not the other ones like properties (with #Input)...
See this plunkr: https://plnkr.co/edit/PrAvliIpWTNFAtH33bHA?p=preview.
I am quite new to TypeScript and I experience a strange problem at the moment. I create an instance of my main class when the document is ready, using JQuery.
var main: MainApp;
$(document).ready(function () {
main = new MainApp();
});
The simplified MainApp Class:
class MainApp {
// Helper Objects
net: AppNetworking;
urlHelper: UrlHelper;
cat: Category;
// Construction
constructor() {
this.net = new AppNetworking();
this.urlHelper = new UrlHelper();
}
// Ajax Callback with Data needed to initialize the "cat" object
private AjaxCallback(categoryData){
this.cat = new Category(categoryData);
}
// Event Handler for an HTML-Element
// As it would be called anonymously in JS I decided to make it a static function
static onClickSendButton(): void{
// Using some members of the MainApp
var hostUrl: string = main.urlHelper.getQueryStringParam("HostUrl");
if (main.cat.isValidCategory()) {
main.sendCategory();
}
}
sendCategory(): boolean {
// Some logic to send data via AJAX
}
}
The function is being registered to the onClick Event of a Button on construction of the MainApp Class.
$("#btnSendCat").click(MainApp.onClickSendButton);
When the function onClickSendButton() gets called, it produces the error:
Uncaught TypeError: Cannot read property 'isValidCategory' of undefined
When debugging, the urlHelper Instance is defined, but the cat Instance is undefined. As I do not touch the instance cat anywhere in my application, I'm really confused how it is undefined. Also when checking the main variable all members are defined!
Am I doing anything illegal here? Could there be issues with that code?
Completely revised answer. I actually answered with the two most common scenarios for this error, but actually your problem is different.
The usual answers are
Make sure you are referencing .js files, not .ts files
Make sure you are loading scripts in the correct order
In your case, this is not the problem and your code is sufficient to recreate the issue.
I have put together the following test, filling in the blanks - and it works as expected.
app.ts
declare var main: MainApp;
class AppNetworking {
}
class UrlHelper {
getQueryStringParam(input: string) {
console.log('Got here getQueryStringParam');
return input;
}
}
class Category {
isValidCategory() {
console.log('Got here isValidCategory');
return true;
}
}
class MainApp {
// Helper Objects
net: AppNetworking;
urlHelper: UrlHelper;
cat: Category;
// Construction
constructor() {
this.net = new AppNetworking();
this.cat = new Category();
this.urlHelper = new UrlHelper();
}
// Event Handler for an HTML-Element
// As it would be called anonymously in JS I decided to make it a static function
static onClickSendButton(): void{
// Using some members of the MainApp
var hostUrl: string = main.urlHelper.getQueryStringParam("HostUrl");
if (main.cat.isValidCategory()) {
main.sendCategory();
}
}
sendCategory(): boolean {
// Some logic to send data via AJAX
return true;
}
}
index.html snip
<div id="btnSendCat">BTN SEND CAT</div>
<script src="app.js"></script>
<script src="Scripts/jquery-2.1.1.min.js"></script>
<script>
var main;
$(document).ready(function () {
main = new MainApp();
$("#btnSendCat").click(MainApp.onClickSendButton);
});
</script>
The result of running this test is the following output in the console window:
"Got here getQueryStringParam" app.js:10
"Got here isValidCategory" app.js:19
I left some important parts of my App out, I'm sorry. Later in the project I used to reinitialize that Category Object. This re initialization was done in an AJAX-Callback Function. This function runs outside of my Object and this wont be my MainApp Class but the Window. I think it's what you call an anonymous function in JavaScript.
I fixed that issue by taking use of my global main Variable
class MainApp {
// Called anonymous so it should be a static function
private AjaxCallback(categoryData){
// this.cat = new Category(categoryData); ! this will be the Window Instance and not a MainApp Instance
main.cat = new Category(categoryData); // Initialization using the "main" variable
}
}
The call in my onClickSendButton Method to this.cat succeeds now, as this.cat was reinitialized correctly.
This video helped me a lot in my researches: Understanding "this" in TypeScript