14

I have written this code and am currently wrangling with a bug in an onClick event. I have two events, the onClick event on the child element and the onChange event on the top-level parent element.

The expected behaviour should be to change the activeAccount variable currently held in the Container component. To do this, I added an onClick handler on the AccountRow component that should then call the top-level parent's onChange function.

However, the line

'this.props.onChange(this.props.account)',

in

handleClick: function(e) {
        this.props.onChange(this.props.account);
    },    

meant to call the parent function with parameter 'this.props.account',

gives me this error:

Uncaught TypeError: this.props.onChange is not a function.

I initially thought that this was a scoping issue, but I have added

{...this.props}

on every child and nested child within the parent Container component. So, all props should have been propagated down to the AccountRow component. Nevertheless, the problem still remains.

    var ACCOUNTS = [
    {name: 'cash on hand'},
    {name: 'savings account'},
    {name: 'shared account'},
    {name: 'my second wallet'}
];

var TRANSACTIONS = [
    {date: '', name: 'Bananas', amount: 6, category: 'Groceries', account: 'cash on hand'},
    {date: '', name: 'Apples', amount: 2.50, category: 'Groceries', account: 'cash on hand'},
    {date: '', name: 'Cash withdrawal', amount: 250, account: 'savings account'}
];

var AccountRow = React.createClass({

    handleClick: function(e) {
        this.props.onChange(this.props.account);
    },

    render: function () {
        return (
            <li onClick = {this.handleClick}> {this.props.account}</li>
        )}
});

var AccountList = React.createClass({

    render: function () {

        var rows = [];
        this.props.accounts.map(function(each_account) {
            rows.push(
                <AccountRow 
                    account = {each_account.name} 
                    key = {each_account.name}
                    {...this.props}     
                />);
                      })
    return (
        <ul>
        {rows}
        </ul>
    )   
    }   
});


var NavBar = React.createClass({

    render: function () {
        return (
            <div id = 'account-tabs'>
                <h2> Accounts </h2>
                <AccountList 
                    accounts = {this.props.accounts} 
                    {...this.props} 
                />
            </div>
        )
    }
});

var TransactionRow = React.createClass({
    render: function (){
        var trans = this.props.transaction;

        return (
            <tr>
            <td>{trans.date}</td>
            <td>{trans.name}</td>
            <td>{trans.amount}</td>
            <td>{trans.account}</td>
            <td><a href = ''>edit</a></td>
            </tr>
        )
    }
});

var TransactionList = React.createClass ({                      
    render: function () {

        var activeaccount = this.props.activeAccount;

        var rows = [];
        this.props.transactions.map(function(each_transaction) {
            if (each_transaction.account == activeaccount) {

                /* Very strange behaviour
                if (each_transaction account == this.props.activeAccount) 
                DOES NOT WORK, I do not know why this is the case
                */

                rows.push(<TransactionRow transaction = {each_transaction} key = {each_transaction.name} />);
            }
            else {
                /*console.log(each_transaction.account);*/
            }     
        })
        return (
            <tbody>
            {rows}
            </tbody>
        )
    }
});

var TransactionsTable = React.createClass({
    render: function() {

        return (
            <div id = 'recent-transactions'>
            <h2>Recent Transactions for {this.props.activeAccount}</h2>
            <table>
                <tr>
                    <th>date</th>
                    <th>name</th>
                    <th>amount</th>
                    <th>account</th>
                    <th>edit </th>
                </tr>
                <TransactionList 
                    transactions = {this.props.transactions}
                    activeAccount = {this.props.activeAccount}
            />
            </table>
            </div>
        )
    }
});

var TransactionForm = React.createClass({
    render: function() {
        return (
            <div>
            <h2>Add Transaction </h2>
            <p>Transaction name</p>
            <input type = 'text' />
            <p>Price</p>
            <input type = 'number'/>
            <p>Category (optional) </p>
            <input type = 'text' />
            </div>
        )
    }
});


var ButtonMenu = React.createClass ({
    render: function () {
        return (
            <div>
            <button>Add Transaction</button>
            </div>
        )
    }
});

var Container = React.createClass({

    getInitialState: function (){
        return {
            activeAccount: ACCOUNTS[0].name
        };
    },

    setActiveAccount: function(dat) {
        this.setState({
            activeAccount: dat  
        });
    },

    render: function () {
        return (
            <div id = 'wrapper'>
            <NavBar 
                accounts = {ACCOUNTS}
                activeAccount = {this.state.activeAccount}
                onChange = {this.setActiveAccount}
                {...this.props}
            />
            <TransactionsTable 
                transactions = {TRANSACTIONS}
                activeAccount = {this.state.activeAccount}
            />
            <TransactionForm />
            <ButtonMenu />
            </div>
        );
    }
});


React.render(<Container />, document.body);

Thank you for your time.

2
  • 1
    Are you sure this.props actually has an onChange callback function? Commented May 1, 2015 at 8:50
  • I believe I have it in the <NavBar> component, under Container's render function. Commented May 1, 2015 at 8:54

1 Answer 1

10

The problem is caused by map function, you should pass in this for thisArg when calling map:

this.props.accounts.map(function(each_account) {
  rows.push(
    <AccountRow 
      account = {each_account.name} 
      key = {each_account.name}
      {...this.props}     
    />);
 }, this);

However, this will cause AccountRow to have redundant variables like accounts and activeAccount. I think you should consider transfer only the onChange function:

 <AccountRow 
     account = {each_account.name} 
     key = {each_account.name}
     onChange = {this.props.onChange}
 />
Sign up to request clarification or add additional context in comments.

3 Comments

Good catch! How do I pass the onChange function down the chain? Do I create a new function, like <AccountRow onChange2 = {function} />); which then calls the top level onChange function? Or is there a way to pass the onChange method via props?
Thank you for your help! Can you explain how the , this appended at the end of rows.push function helps to solve this problem?
map is defined as arr.map(callback[, thisArg]). If you don't pass thisArgs, it will use undefined as this in the callback block and this.props will be incorrect.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.