Received NaN for the `children` attribute. If this is expected, cast the value to a string

Viewed 16173

React newbie here. I'm trying to decrement the quantity value upon the button click. When I click the button I see a NaN value displayed next to all the buttons. How could I avoid the NaN error? What mistake am I making here and how would I individually update the values on the buttons? Thank you. Data and code follows. enter image description here

{
    "id": 1,
    "title": "Python Crash Course",
    "author": "Eric Matthes",
    "quantity": 5
  },
  {
    "id": 2,
    "title": "Head-First Python",
    "author": "Paul Barry",
    "quantity": 2
  },
  {
    "id": 3,
    "title": "Invent Your Own Computer Games with Python",
    "author": "Al Sweigart",
    "quantity": 1
  }

Below is my React code.

class BooksComponent extends Component{

    constructor(){
        super()
        this.state ={
            booksData: []
        }
        this.reserve = this.reserve.bind(this)
    }
    
    componentDidMount() {
        axios.get('/library')
          .then(res => {
            const booksData = res.data
            this.setState({ booksData })
          })
    }
    
    render() {
        return (
          <div className="App">
            {this.state.booksData.map(book => {
                const {id, title, author, quantity} = book
                return (
                  <div>
                  <p>{id} - {title} - {author}</p>
                  <button onClick={this.reserve}>Reserve {quantity}</button>
                  <span>{this.state.quantity}</span>
                </div>
                );
            })
            }
          </div>
        )
    }

    reserve(){
        console.log('Reserved')
        this.setState({
            quantity: this.state.booksData.quantity-1
        }
        )
        console.log(this.state)
    }
}



export default BooksComponent
3 Answers

The problem is in the reserve funciton.

this.state.booksData.quantity - 1 

this.state.booksData is an array and is not possible to access quantity proper.

this.state.booksData.quantity is undefined

undefined - number = NaN

You need to filter the array by the id of the bookData item, like this:

reserve(id) {
    this.setState({
        quantity: this.state.booksData.find(item => item.id === id).quantity - 1
    })
}

And in the onClick pass the id of the bookData item.

how would I individually update the values on the buttons?

You can try:

reserve(id) {
    this.setState({
        booksData: this.state.booksData.map(item => {
            if (item.id === id) {
                return { ...item, quantity: item.quantity - 1};
            } else {
                return item;
            }
        })
    })
}

And in the render:

<span>{quantity}</span>

You can update the state inline, below is an example:

class BooksComponent extends Component{

    constructor(){
        super()
        this.state ={
            booksData: []
        }
        this.reserve = this.reserve.bind(this)
    }
    
    componentDidMount() {
        axios.get('/library')
          .then(res => {
            const booksData = res.data
            this.setState({ booksData })
          })
    }
    
    render() {
        return (
          <div className="App">
            {this.state.booksData.map(book => {
                const {id, title, author, quantity} = book
                return (
                  <div>
                  <p>{id} - {title} - {author}</p>
                  <button onClick={()=>this.setState({this.state.booksData: this.state.booksData.map(book => book.id === id ? book.quantity - 1 : book)})}>Reserve {quantity}</button>
                  <span>{this.state.quantity}</span>
                </div>
                );
            })
            }
          </div>
        )
    }

}

Generally (from a higher level) you want to revise your state, and how it is updated. In most cases, your initialState has an undefined value, and calculations in the subscriber are being done using the undefined value.

For my case though, I had initial state defined in the reducer and the subscriber had the default value (initially), but a dispatched action in componentDidUpdate (from one of the earlier re-renders in the publisher) would reassign the said value to undefined (hence the warning is thrown in the console). However, later re-renders (when the network request is successful) would dispatch an action to reassign with an actual value!

Messy state management, but you get the point! What I did was to add an equality check for prevState and this.state for the particular fields, and the error was resolved. I later improved my state management to avoid such complexity.

Related