reactjstypescriptreact-routerreact-router-domfetch-api

Value comes back 'undefined' on first component render


I am attempting to do a book search with the bookTitle that users type in or select from the page. On first render bookTitle comes back undefined, which then pulls up some random kids book called 'undefined'. By putting the bookTitle in the dependency array of my useEffect, I expected this issue to be resolved, but now this causes it to flash the undefined book, then the desired book, and then back to undefined.

const location = useLocation()
const { data } = location.state
const [apiData, setApiData] = useState<GoogleBooks>()
const [bookTitle, setBookTitle] = useState()

const squishedTitle = data.content.title.replace(/ /g, '').toLowerCase()

useEffect(() => {
  setBookTitle(squishedTitle)
  fetch(`https://www.googleapis.com/books/v1/volumes?q=${bookTitle}&key=${import.meta.env.VITE_GOOGLE_API_KEY}`)
    .then(res => res.json())
    .then(response => setApiData(response))
    .catch((err) => console.error(err))
}, [bookTitle])

I am expecting to never receive undefined when a user clicks on a book.


Solution

  • The code has defined bookTitle to be initially undefined.

    const [bookTitle, setBookTitle] = useState(); // <-- undefined
    

    This means that for the initial render cycle that bookTitle is undefined, and then at the end of the render cycle the effect runs and enqueues a state update to update bookTitle to the value of squishedTitle and that bookTitle is undefined for the fetch request.

    Just initialize the bookTitle state to the computed value directly so it's available on the initial render cycle.

    const { state } = useLocation();
    
    const [apiData, setApiData] = useState<GoogleBooks>();
    const [bookTitle, setBookTitle] = useState(
      state?.data.content.title.replace(/ /g, '').toLowerCase() || ""
    );
    
    useEffect(() => {
      fetch(`https://www.googleapis.com/books/v1/volumes?q=${bookTitle}&key=${import.meta.env.VITE_GOOGLE_API_KEY}`)
        .then(res => res.json())
        .then(response => setApiData(response))
        .catch((err) => console.error(err))
    }, [bookTitle]);
    

    If you are not modifying the bookTitle state elsewhere then it may be preferable to use the data value directly as a dependency and forego the local state.

    Example:

    const { state } = useLocation();
    
    const [apiData, setApiData] = useState<GoogleBooks>();
    
    const bookTitle = state?.data.content.title.replace(/ /g, '').toLowerCase() || "";
    
    useEffect(() => {
      fetch(`https://www.googleapis.com/books/v1/volumes?q=${bookTitle}&key=${import.meta.env.VITE_GOOGLE_API_KEY}`)
        .then(res => res.json())
        .then(response => setApiData(response))
        .catch((err) => console.error(err))
    }, [bookTitle]);