React onClick() doesn't trigger when inside map function

Viewed 1938

I'm building my first react application and I've got an issue.

I want to create a simple app where I can click on a book title in a sidebar, and see a summary of the book. I'm getting the book titles and summaries through an API I've defined locally.

Right now, clicking on a book title doesn't do anything. I get an error in red in the console:

index.js:1 Warning: Each child in a list should have a unique "key" prop.

I'm calling my API like so:

  const [viewingId, setViewingId] = useState()
  const [bookList, setBookList] = useState([])
  const [contents, setContents] = useState({})

  useEffect(() => {

    fetch("http://localhost:5000/books")
    .then(res => res.json())
    .then(res => {setBookList(res)})
  }, [])

  const fetchBookSummary = (id) => {
    fetch(`http://localhost:5000/book/${id}`)
    .then(res => res.json())
    .then(res => {
      setContents({...contents, [id]: res[0]})
    })
  }

This is how I'm defining my 'onClick' function:

  const seeSummary = (id) => {
    fetchBookSummary(id)
    setViewingId(id)
  }

And finally, my App renders like this:

  return (
    <div className="App">
      <div className="App-Container">
        <div className="BookList">
          {bookList.map(book => (
            <Node book={book} onClick={() => seeSummary(book.id)} />
          ))}
        </div>

        <div className="SummaryView">
          {contents[viewingId]?.content?.map((summaryData => (
              <Summary summaryData={summaryData}/>
          )))}
        </div>
      </div>
    </div>
  );
}

I used to have just onClick={seeSummary(book.id)}, but I got a "too many re-renders" error...I'm not sure I understand what adding () => did.

I'd like to be able to see the Summary when I click on a book. Any help is appreciated!

1 Answers

To resolve the React key warning you just need to provide a valid React key to the elements being mapped.

{bookList.map(book => (
  <Node
    key={book.id} // <-- Add a unique key, the book id is good
    book={book}
    onClick={() => seeSummary(book.id)}
  />
))}

When you were just onClick={seeSummary(book.id)} this was invoking the callback immediately, thus causing the render looping. By adding the "() =>" you are declaring an anonymous function to invoke your seeSummary function and pass the specific book id at-the-time the node is clicked.

I don't see any overt issues with the way you've defined your onClick handler other than the fetching book details will take longer than it will take to update the summary id.

const seeSummary = (id) => {
  fetchBookSummary(id); // fetching data
  setViewingId(id); // will update first.
}

I suspect you haven't connected/attached the onClick prop of the Node component to any DOM element, like a button or div, etc... You need to ensure that the onClick prop is actually attached to something a user can interact with.

Example:

const Node = ({ book, onClick }) => (
  <div onClick={onClick}>
    {book.title} - Click me to see summary!
  </div>
);
Related