Search code examples
ecmascript-6reactjsmeteor-react

React Meteor cannot seem to pass props


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,
};

Solution

  • 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,
    };