Search code examples
javascriptjsonreactjsreact-hooksreact-component

In React, how do I properly use hooks to update one component when I trigger an Add function in another component?


I am building an app for finding books in order to learn React. I cannot figure out how to add a book from my list of all books ("Book List") into a separate "Reading List". I have a JSON data file of books that looks like this:

[
  {
    "descr": "Take a voyeuristic look at life in Sheffield...",
    "author": "Philip Hensher",
    "title_id": "9812",
    "title": "The Northern Clemency",
    "cover": "https://www.whichbook.net/assets/images/big/9812.jpg",
    "similar": "https://www.whichbook.net/#cmd=search&search-type=3&id=9812"
  },
  ...
]

and I loop through them in my BookList.js component to display each book and a button to "Add to Reading List" like so:

export default function BookList ({ handleAddBook }) {
    return (
        <div>
            {BookData.map((book) => {
                return (
                <>
                    <BookDetail key={book.title_id} book={book} />
                    <button onClick={handleAddBook}>Add to Reading List</button>
                </>
                )
            })}
        </div>
    )
}

For each BookDetail, I simply display the title and title_id. In BookDetail.js:

export default function BookDetail({ book }) {
    return (
        <div>
            <h1>{book.title}</h1>
            <p>{book.title_id}</p>
        </div>
    )
}

This works great. I see a list of all my books. Now, in my ReadingList.js I attempt to loop through books similar to my BookList, except the functional component has a booksToRead argument:

export default function ReadingList({ booksToRead }) {
    return (
        <div>
            <h3>My Reading List</h3>
            {booksToRead.map((book) => {
                return (
                <>
                    <BookDetail key={book.title_id} book={book} />
                </>
                )
            })}
        </div>
    )
}

Ok, so now let's look at App.js where I have attempted to work this out. Basically I am trying to use useState() and useEffect() to update my booksToRead array whenever the constant newBookToRead changes, which is handled in the handleAddBook function. I initialized booksToRead with dummy data, and it displays in the browser, but never updates.

function App() {

    const [books, setBooks] = useState([])
    const [booksToRead, setBooksToRead] = useState([{ title: 'initialBookToRead', title_id: '1234' }])
    const [newBookToRead, setNewBookToRead] = useState([{ title: 'addMe', title_id: '000' }])

    function handleAddBook() {
        setNewBookToRead(newBookToRead)
    }

    useEffect(() => {
        setBooksToRead(booksToRead => [...booksToRead, newBookToRead])
    }, [newBookToRead])


  return (
    <main>
        <BookList books={books} />
        <ReadingList booksToRead={booksToRead} />
    </main>
  );
}

export default App;

I've tried moving the logic around to the different components, I've tried creating a ReadingListContext with createContext and using ReadingListContext.Provider value={booksToRead} instead of the <ReadingList> tag, I just can't figure it out.

Also, potentially unrelated, my console gives me Warning: Each child in a list should have a unique "key" prop. for both BookList and ReadingList, even though I set the key to book.title_id which is a unique code. ????


Solution

  • export default function BookList ({ books, handleAddBook }) {
    return (
        <div>
            {books.map((book) => {
                const onAddBook = () => handleAddBook(book);
                return (
                <>
                    <BookDetail key={book.title_id} book={book} />
                    <button onClick={onAddBook}>Add to Reading List</button>
                </>
                )
            })}
        </div>
    )
    }
    
    function App() {
    
        const [books, setBooks] = useState([])
        const [booksToRead, setBooksToRead] = useState([{ title: 'initialBookToRead', title_id: '1234' }])
    
        function handleAddBook(book) {
            setBooksToRead([...booksToRead, book])
        }
    
    
      return (
        <main>
            <BookList books={books} handleAddBook={handleAddBook}/>
            <ReadingList booksToRead={booksToRead} />
        </main>
      );
    
    }
    
    export default App;
    

    Please use above components. You need to pass book as param to handleAddBook. No need to store newBookToRead and useEffect you can use handleAddBook as mentioned.