3

I am currently trying to create a component that onclick of a button gets appended to a parent component of DOM element. However I am having a problem getting the initial loop working. Here is what I am doing,

 class GenerateInvoice extends Component {

  constructor(props) {
    super(props);
    this.state = {
      'invoice': {
        'items' : {}
      }
    };

    this.onAddChild = this.onAddChild.bind(this);
  }

  render() {
    const children = [];
    for (var i = 0; i < Object.keys(this.state.invoice.items); i += 1) {
      children.push(<InvoiceItemForm key={i} number={i} />);
    };
    return(
      <div>
        <a href="" onClick={this.onAddChild}>Add New Item</a>
        {children}
      </div>
    )
  }

  onAddChild = (e) => {

    e.preventDefault();
    let invoice = this.state.invoice.items;
    this.setState({ invoice : {'id' : 'INV001'} });
  }


}

export default GenerateInvoice ;

However when I client the button with onAddChild callback on it, I get

Cannot convert undefined or null to object

why would this be?

Here is a link to my test project,

https://codesandbox.io/s/0qx9w1qrwv

5
  • Your state objects keys don't need to be strings Commented Oct 12, 2017 at 9:57
  • 1
    @KarlTaylor: They aren't. The property names are given as strings, true, which is optional but harmless. Commented Oct 12, 2017 at 9:58
  • Please update your question with a runnable minimal reproducible example demonstrating the problem, using Stack Snippets (the [<>] toolbar button). Stack Snippets support React, including JSX; here's how to do one. Commented Oct 12, 2017 at 9:59
  • 3
    Side note: The loop creating the children is markedly inefficient, and it's really strange to be using the index of the property name in an object (i in that loop) for...well...for just about anything. I suspect this.state.invoice.items should be an array, not an object. Also note that the block attached to a control statement (if, for, etc.) doesn't take a ; at the end. (The parser will ignore it because it allows EmptyStatement, but it doesn't belong there.) Commented Oct 12, 2017 at 10:00
  • Could you please explain why it is inefficient? Commented Oct 12, 2017 at 10:13

1 Answer 1

2

You are overwriting your state here:

let invoice = this.state.invoice.items;
this.setState({ invoice : {'id' : 'INV001'} });

after that call your state will be

{ invoice: {'id': 'INV001'} }

and the items property will be gone.

If you are trying to add an item, something like this would work:

let invoice = this.state.invoice;
// creates an updated version of the items without changing the original value
let updatedItems = invoice.items.concat({'id': 'INV001'});
// creates a new version of the invoice with the updated items
let updateInvoice = { ...invoice, items: updatedItems };
// update the invoice on the state to the new version
this.setState({ invoice });
Sign up to request clarification or add additional context in comments.

2 Comments

This is assuming items is changed to an array as T.J. Crowder suggested above
Changed to array and it works great :), though I would like some input as to why my loop is inefficient?

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.