Where did my data go? {React Router}
Allison Kim
Posted on August 26, 2022
In this post, I'll be talking about an intricate nest of problems I dealt with while learning how to use React Router that took some time debugging and troubleshooting to understand.
To set the stage, a fellow developer and I were tasked with building a single-page application that utilizes client-side routing to render a different 'page' on the DOM with each route.
We built an application that accesses a free Pokémon API to render a Pokémon library as one route and a personal Pokédex as another route. The library lists the first 386 Pokémon from the API and the Pokédex lists whatever Pokémon the user saved (or caught, if you will). The user can also click on any of the Pokémon to link and route to a detailed page of that Pokémon. Each route has its own unique URL, including each route to a specific Pokémon (i.e. clicking on the Pikachu would change the URL to localhost:####/pikachu).
That was all well and done -- until we tried routing to the detailed Pokémon page by manually typing a Pokémon in the URL. The Pokémon data was not passing down and the page would show nothing. That begged the question: where did our data go???
To illustrate our framework:
Our parent App component holds the routing to our Library, Pokédex, and Pokémon Page components. In App, we fetch the data from the API in a useEffect hook and store that data in state. We then pass this data to all three child components as props so it's available to render data in those components.
function App() {
... //fetched data
return (
<div>
<NavBar />
<Switch>
<Route exact path ="/">
<HomePage />
</Route>
<Route exact path="/library">
<Library
pokemons={pokemons} //passing props to Library
/>
</Route>
<Route exact path="/pokedex">
<Pokedex
pokemons={pokemons} //passing props to Pokedex
/>
</Route>
<Route exact path="/:name">
<PokemonPage
pokemons={pokemons} //passing props to PokemonPage
/>
</Route>
</Switch>
</div>
);
What's funny is when we manually type in the URL for the Library and Pokédex components, they load as expected. It's only when we manually type in the URL for a specific Pokémon that the Pokémon Page renders nothing.
To troubleshoot, I set several debuggers in my parent and child components to try and follow the path the code was taking. I also placed a console log of the props in the Pokémon Page component to check if it was coming through (it wasn't) - as well as console logs in certain places to see what lines of code were hitting. That's when I noticed something interesting.
Important background info:
When we initially fetch the data in the App component, we have it so there are two fetches happening asynchronously. This was necessary because of how the API is set up. The first fetch only provides the Pokémon names, so we use a second fetch to iterate through the names to get each Pokémon's details. Because the first fetch needs to finish before the second fetch can start, we leveraged async and await to accomplish this. This caused state to be set continuously, making our code go back and forth between rendering our App and Pokémon Page components so that Pokémon Page would only load chunks of data at a time.
function App() {
const getPokemons = async () => {
const res = await fetch({pokeAPI url})
const data = await res.json()
const createPokemonObject = (results) => {
results.map(async pokemon => {
const res = await fetch({pokeAPI url by pokemon name})
const data = await res.json()
setPokemons(pokemons => [...pokemons, data])
})
}
createPokemonObject(data.results)
}
useEffect(() => {
getPokemons()
}, [])
... //returned JSX
}
The reason this stifled the Pokémon Page component is because the delay in fetching led the component to first load with empty props. This prevented our image elements from rendering due to undefined sources (which would've come from the props) and in turn stopped our component from rendering altogether. Even though we know the data will technically be sent later, the component doesn't. So it doesn't try to re-render the component after it thinks it failed the first time.
The solution I came up with to resolve this was to apply an if statement that wraps our JSX to return a different JSX (i.e. 'Loading...' text) if props are undefined, else return our original detailed page JSX. This allowed the page to continue attempting to render the asynchronously fetched data by tricking the component into thinking it's not failing anything so keep trying.
if (pokemon === undefined) {
return <h1>Loading...</h1>
} else {
return (
... //JSX for the detailed page
)
}
Posted on August 26, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 18, 2024