So I have been trying to teach myself react and meteor to create apps. Since this is my first real dive into this material I may be missing something very simple. The full code can be found at https://github.com/Afro523/MineralID-Meteor.
I have a collection in mongo called Minerals and I am pretty sure that the data itself loads in from the publication to the subscription from watching the meteor dev tools ddp and minimongo.
I am using React router to go to ListPage.jsx and then load my collection of minerals. I run my code (below) and get "Uncaught TypeError: Cannot read property 'minName' of undefined"
I hope I provided enough detail of the situation, any and all suggestions are very much appreciated. Thank you in advanced!
ListPage.jsx i.e. landing page
import React, { Component, PropTypes } from 'react';
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import {List} from 'material-ui/List';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
import {Link} from 'react-router';
import {createContainer} from 'meteor/react-meteor-data';
import {Meteor} from 'meteor/meteor';
import {Minerals} from '../../api/minerals';
import MinList from './MinList';
// App component - represents the whole app
export class ListPage extends Component {
constructor(props) {
super(props);
}
getChildContext() {
return { muiTheme: getMuiTheme(baseTheme) };
}
renderMinerals () {
return this.props.minerals.map((mineral) => (
<MinList key={mineral._id} mineral={mineral}/>
));
}
render() {
return (
<div className="container">
<AppBar
iconElementLeft={<IconButton><Link to="/"><NavigationClose/></Link></IconButton>}
title="Mineral ID"
/>
<List>
{ this.renderMinerals() }
</List>
</div>
);
}
}
ListPage.propTypes = {
minerals: PropTypes.array.isRequired,
};
export default createContainer(()=>{
if (Meteor.subscribe('minerals').ready()){
return{
minerals: Minerals.find({}, {sort: {name: 1}, limit:10}).fetch(),
};
} else {
return{
minerals: null
};
}
}, MinList);
ListPage.childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
};
MinList.jsx
import React, {Component, PropTypes} from 'react';
import {ListItem} from 'material-ui/List';
import Avatar from 'material-ui/Avatar';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import MinCard from './MinCard';
const customContentStyle = {
width: '95%',
maxWidth: 'none',
};
export default class MinList extends Component {
constructor(props) {
super(props);
//Setting up state for dialog
this.state = {
open:false
};
}
handleOpen() {
this.setState({open: true});
}
handleClose() {
this.setState({open: false});
}
render() {
const mineral = this.props.mineral;
const actions = [
<FlatButton
label="Close"
primary={true}
onTouchTap={this.handleClose.bind(this)}
/>,
];
return (
<div>
<ListItem
primaryText={mineral.minName}
leftAvatar={<Avatar src={'./img/'+mineral.minName+'.jpg'}/>}
secondaryText={mineral.formula}
onTouchTap={this.handleOpen.bind(this)}
/>
<Dialog
title={mineral.minName}
leftAvatar={<Avatar src="minImage.jpg"/>}
actions={actions}
modal={false}
open={this.state.open}
onRequestClose={this.handleClose.bind(this)}
autoScrollBodyContent={true}
contentStyle={customContentStyle}
>
<MinCard mineral={mineral}/>
</Dialog>
</div>
);
}
}
MinList.propTypes ={
minerals: PropTypes.array.isRequired,
mineral : PropTypes.object.isRequired,
};
MinCard.jsx lowest level component
import React, {Component, PropTypes} from 'react';
import {Card, CardMedia, CardText} from 'material-ui/Card';
import {Table, TableBody, TableRow, TableRowColumn} from 'material-ui/Table';
const tableStyle={
fontSize: '15px',
};
export default class MinCard extends Component {
render() {
const mineral = this.props.mineral;
return (
<Card>
<CardMedia mediaStyle={{height: '50%', width: '50%', margin: 'auto'}}>
<img src={'./img/'+this.props.mineral.minName+'.jpg'}/>
</CardMedia>
<CardText>
<h5>Summary</h5>
{mineral.summary}
</CardText>
<Table>
<TableBody
displayRowCheckbox={false}
>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Formula
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.formula}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Crystal System
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.crystalSystem}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Crystal Habit
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.crystalHabit}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Cleavage
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.cleavage}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Luster
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.luster}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Color
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.color}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Streak
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.streak}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Class Type
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.classType}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Fracture
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.fracture}
</TableRowColumn>
</TableRow>
<TableRow
selectable={false}
>
<TableRowColumn style={tableStyle}>
Hardness
</TableRowColumn>
<TableRowColumn style={tableStyle}>
{mineral.hardness}
</TableRowColumn>
</TableRow>
</TableBody>
</Table>
</Card>
);
}
}
MinCard.propTypes = {
mineral: PropTypes.object.isRequired,
};
First off thank you Paqash for pointing me in the right direction, the problem was that the component was mounting before the data was available. So after some research I found this topic https://forums.meteor.com/t/react-component-mount-wait-for-subscriptions-ready/13646.
I needed a getMeteorData() function and from there I could control what happens when the subscription is ready and not ready. And due to this function I also had to change the map function to this.data.minerals, the changed page is below. I hope this helps others looking for solutions to this problem.
import React, { Component, PropTypes } from 'react';
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import {List} from 'material-ui/List';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
import {Link} from 'react-router';
import {ReactMeteorData} from 'meteor/react-meteor-data';
import {Meteor} from 'meteor/meteor';
import {Minerals} from '../../api/minerals';
import MinList from './MinList';
import ReactMixin from 'react-mixin';
export default class ListPage extends Component {
constructor(props) {
super(props);
}
getChildContext() {
return { muiTheme: getMuiTheme(baseTheme) };
}
//New Function Needed
getMeteorData(){
const handle = Meteor.subscribe('minerals');
return {
ready: handle.ready(),
minerals: Minerals.find({}, {sort: {name: 1}}).fetch(),
};
}
renderMinerals () {
return this.data.minerals.map((mineral) => (
<MinList key={mineral._id} mineral={mineral}/>
));
}
render() {
//Wrapped render in if data ready bool
if(!this.data.ready){
return (
<div className="container">
<AppBar
iconElementLeft={<IconButton><Link to="/"><NavigationClose/></Link></IconButton>}
title="Mineral ID"
/>
<div>Loading</div>
</div>
);
} else {
return (
<div className="container">
<AppBar
iconElementLeft={<IconButton><Link to="/"><NavigationClose/></Link></IconButton>}
title="Mineral ID"
/>
<List>
{ this.renderMinerals() }
</List>
</div>
);
}
}
}
//Added
ReactMixin(ListPage.prototype, ReactMeteorData);
ListPage.propTypes = {
minerals: PropTypes.array.isRequired,
};
ListPage.childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
};