feat: add cleanup logic for weather api fetch using useEffect - #9
SaumyaDwivedi179 wants to merge 1 commit into
Conversation
Change SummaryThis PR refactors the weather search flow to store the searched Note: The PR description mentions console logging and keeping existing components “fully functional”; the diff does not add logs and removes theme/toggle-related behavior. Also, a large block of the previous File Changes
|
PR ScorecardScoreScoring MethodologyCommunication ScoreThe overall communication score is a weighted average:
Formula: Scoring FrameworkThe scorecard evaluates code using 3 key reviewer questions:
Each category score is the average of its subcategories. Score Bands: 90-100 Excellent | 75-89 Good | 60-74 Adequate | 40-59 Needs Work | 0-39 Critical PR Communication NotesDescription Quality
PR Size & Scope
Commit Messages
NotesCode Correctness & Design Quality
Test Quality & Coverage
Code Readability & Maintainability
|
| const handleSearch = (cityNameInput) => { | ||
| setCityName(cityNameInput.toLowerCase().trim()) | ||
| } |
There was a problem hiding this comment.
Severity: 🟡 Moderate
Robustness: Same-city searches may not re-fetch (state set to same value can be skipped)
handleSearch sets cityName to a normalized string. If the user searches the same city twice, React may bail out (state value unchanged), so the effect won’t rerun and the user can’t “retry”.
Why this matters: Users commonly retry searches after transient failures; “nothing happens” is surprising.
Suggested fix (add a monotonically increasing request token):
const [cityName, setCityName] = useState('')
const [searchRequestId, setSearchRequestId] = useState(0)
const handleSearch = (cityNameInput) => {
setCityName(cityNameInput.toLowerCase().trim())
setSearchRequestId((id) => id + 1)
}
useEffect(() => {
// ... fetch logic
}, [cityName, searchRequestId])| useEffect(() => { | ||
| if (!cityName) return | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } | ||
| setCurrentWeather(formattedWeather) | ||
| setWeatherReports(formattedWeather) | ||
| } catch (fetchError) { | ||
| setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() | ||
|
|
||
| return () => { | ||
| ignore = true | ||
| } | ||
| }, [cityName]) |
There was a problem hiding this comment.
Severity: 🟡 Moderate
Correctness: PR description mentions console logging, but code has none
The PR description says fetch start/success/error/cleanup are logged for verification, but the effect currently doesn’t log anything.
Why this matters: Reviewers following the stated manual testing steps can’t validate the behavior as described, and it creates confusion about what’s intended to ship.
Suggested fix (if you want logs, gate them to dev to avoid noisy prod consoles):
const isDebug = import.meta.env.DEV
// before starting
if (isDebug) console.debug(`Fetching weather for: ${cityName}`)
// on success
if (isDebug) console.debug('Weather fetched:', weatherData)
// on error
if (isDebug) console.debug('Weather fetch error:', err)
// in cleanup
return () => {
if (isDebug) console.debug('Cleanup: component unmounted or effect re-runs')
ignore = true
}(Alternatively, update the PR description/testing steps to match the code.)
| function App() { | ||
| const [cityName, setCityName] = useState("") | ||
| const [currentWeather, setCurrentWeather] = useState(null) | ||
| const [weatherReports, setWeatherReports] = useState(null) | ||
| const [isLoading, setIsLoading] = useState(false) | ||
| const [errorMessage, setErrorMessage] = useState(null) | ||
| const [theme, setTheme] = useState('dark') | ||
|
|
||
| const handleSearch = async (cityNameInput) => { | ||
| const cityName = cityNameInput.toLowerCase().trim() | ||
| const coordinates = CITY_COORDS[cityName] | ||
| const handleSearch = (cityNameInput) => { | ||
| setCityName(cityNameInput.toLowerCase().trim()) | ||
| } | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityNameInput}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| return | ||
| } | ||
| useEffect(() => { | ||
| if (!cityName) return | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } | ||
| setCurrentWeather(formattedWeather) | ||
| setWeatherReports(formattedWeather) | ||
| } catch (fetchError) { | ||
| setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() | ||
|
|
||
| return () => { | ||
| ignore = true | ||
| } | ||
| }, [cityName]) |
There was a problem hiding this comment.
Severity: 🟡 Moderate
Structure: Consider extracting fetch orchestration into a small hook (useWeather)
App currently owns input normalization, request lifecycle, error handling, and rendering. This is workable, but it increases coupling and makes race-condition behavior harder to test in isolation.
Why this matters: A dedicated hook makes cancellation/stale-response logic easier to reason about and unit test, and keeps App focused on composition.
Suggested fix (sketch):
function useWeather(cityName) {
const [currentWeather, setCurrentWeather] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState(null)
useEffect(() => {
// move the effect logic here
}, [cityName])
return { currentWeather, isLoading, errorMessage }
}Then App becomes mostly const { currentWeather, isLoading, errorMessage } = useWeather(cityName).
| import styles from './App.module.css' | ||
|
|
||
| function App() { | ||
| const [cityName, setCityName] = useState("") |
There was a problem hiding this comment.
Severity: 💭 Minor [nitpick]
Consistency: Mixed quote style + trailing whitespace
useState("") uses double quotes and has trailing whitespace.
Why this matters: Small consistency issues add noise to diffs and make formatting less predictable.
Suggested fix:
const [cityName, setCityName] = useState('')| useEffect(() => { | ||
| if (!cityName) return | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } |
There was a problem hiding this comment.
Severity: 🔴 Critical
Correctness: isLoading can get stuck true when switching to an invalid city mid-flight
If a valid-city request has already set isLoading(true), and the next search is an invalid city, the new effect returns early without clearing isLoading. The previous request’s finally won’t clear it because it’s gated behind ignore.
Why this matters: Users can get stuck in a permanent loading state even though the latest search is already known to be invalid.
Suggested fix:
if (!coordinates) {
setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`)
setCurrentWeather(null)
setIsLoading(false) // ensure prior in-flight request can't leave the spinner stuck
return
}(If you adopt an AbortController/request-id approach, make sure the same "clear loading on invalid query" behavior is preserved.)
| return ( | ||
| <div className={`${styles.appContainer} ${styles[theme]}`}> | ||
| <div className={styles.appContainer}> | ||
| <div className={styles.mainWrapper}> | ||
| <Header theme={theme} onThemeToggle={handleThemeToggle} /> | ||
| <Header /> | ||
|
|
||
| <main className={styles.mainSection}> | ||
| <SearchForm onSearch={handleSearch} theme={theme} /> | ||
| <SearchForm onSearch={handleSearch} /> | ||
| {isLoading && <p className={styles.statusText}>Searching the skies...</p>} | ||
| {errorMessage && <div className={styles.errorBox}>{errorMessage}</div>} | ||
| {currentWeather && !isLoading && <WeatherCard data={currentWeather} theme={theme} />} | ||
| {currentWeather && !isLoading && <WeatherCard data={currentWeather} />} | ||
| </main> | ||
| <footer className={`${styles.footer} ${styles[theme]}`}> | ||
| Powered by Open-Meteo API | ||
| </footer> | ||
|
|
||
| <footer className={styles.footer}>Powered by Open-Meteo API</footer> | ||
| </div> |
There was a problem hiding this comment.
Severity: 🟠 Major
Correctness: Removed props may break Header/SearchForm/WeatherCard at runtime
Header, SearchForm, and WeatherCard are now rendered without theme and onThemeToggle. If those components expect these props (for styling or behavior), this can cause broken UI or runtime errors.
Why this matters: This PR is scoped to fetch cleanup logic; dropping required props risks shipping a functional regression unrelated to the change.
Suggested fix (restore previous plumbing, or update the child components to not require these props):
const [theme, setTheme] = useState('dark')
const handleThemeToggle = () => {
setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark'))
}
return (
<div className={`${styles.appContainer} ${styles[theme]}`}>
<div className={styles.mainWrapper}>
<Header theme={theme} onThemeToggle={handleThemeToggle} />
<main className={styles.mainSection}>
<SearchForm onSearch={handleSearch} theme={theme} />
{isLoading && <p className={styles.statusText}>Searching the skies...</p>}
{errorMessage && <div className={styles.errorBox}>{errorMessage}</div>}
{currentWeather && !isLoading && <WeatherCard data={currentWeather} theme={theme} />}
</main>
<footer className={`${styles.footer} ${styles[theme]}`}>
Powered by Open-Meteo API
</footer>
</div>
</div>
)| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } | ||
| setCurrentWeather(formattedWeather) | ||
| setWeatherReports(formattedWeather) | ||
| } catch (fetchError) { | ||
| setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() | ||
|
|
||
| return () => { | ||
| ignore = true | ||
| } |
There was a problem hiding this comment.
Severity: 💭 Minor [nitpick]
Clarity: ignore is vague; rename to reflect intent
ignore doesn’t communicate what is being ignored (responses/state updates) or under what condition.
Why this matters: Cleanup/race-condition code is already subtle; clearer naming reduces future mistakes.
Suggested fix:
let shouldIgnoreResponse = false
// ...
if (!shouldIgnoreResponse) {
setCurrentWeather(...)
}
return () => {
shouldIgnoreResponse = true
}| useEffect(() => { | ||
| if (!cityName) return | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } | ||
| setCurrentWeather(formattedWeather) | ||
| setWeatherReports(formattedWeather) | ||
| } catch (fetchError) { | ||
| setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() | ||
|
|
||
| return () => { | ||
| ignore = true | ||
| } | ||
| }, [cityName]) |
There was a problem hiding this comment.
Severity: 🟠 Major
Robustness: Request isn’t cancelled; use AbortController to stop in-flight fetches
The current pattern only ignores state updates; it still performs the network work for stale requests. On rapid searches, this wastes network/CPU and can also keep server-side rate limits hotter than necessary.
Why this matters: Cancelling stale requests reduces wasted work and makes behavior more deterministic under rapid interactions.
Recommended approach (abort on cleanup, and pass signal into the API call):
useEffect(() => {
if (!cityName) return
const controller = new AbortController()
const { signal } = controller
const startFetching = async () => {
const coordinates = CITY_COORDS[cityName]
if (!coordinates) {
setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`)
setCurrentWeather(null)
setIsLoading(false)
return
}
setIsLoading(true)
setErrorMessage(null)
try {
const weatherData = await fetchWeatherInfoByCoordinates(
coordinates.lat,
coordinates.lon,
{ signal }
)
setCurrentWeather({
...weatherData,
city: cityName.charAt(0).toUpperCase() + cityName.slice(1)
})
} catch (err) {
if (err?.name === 'AbortError') return
setErrorMessage(err.message || 'Failed to fetch weather data.')
setCurrentWeather(null)
} finally {
setIsLoading(false)
}
}
void startFetching()
return () => controller.abort()
}, [cityName])This likely requires updating fetchWeatherInfoByCoordinates to accept an optional { signal } param and pass it to fetch(...).
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() |
There was a problem hiding this comment.
Severity: 🟡 Moderate
Best Practices: startFetching() promise is not handled at call site
startFetching() is an async function invoked without await/catch. Even though you have a try/catch inside, any throw that happens before it (or a synchronous throw inside fetchWeatherInfoByCoordinates) can still surface as an unhandled rejection.
Why this matters: Unhandled rejections can fail tests, pollute logs, and make real errors harder to diagnose.
Suggested fix:
startFetching().catch((err) => {
if (ignore) return
setErrorMessage(err?.message || 'Failed to fetch weather data.')
setCurrentWeather(null)
setIsLoading(false)
})(If you move to AbortController, you can also ignore AbortError here.)
| function App() { | ||
| const [cityName, setCityName] = useState("") | ||
| const [currentWeather, setCurrentWeather] = useState(null) | ||
| const [weatherReports, setWeatherReports] = useState(null) | ||
| const [isLoading, setIsLoading] = useState(false) | ||
| const [errorMessage, setErrorMessage] = useState(null) | ||
| const [theme, setTheme] = useState('dark') | ||
|
|
||
| const handleSearch = async (cityNameInput) => { | ||
| const cityName = cityNameInput.toLowerCase().trim() | ||
| const coordinates = CITY_COORDS[cityName] | ||
| const handleSearch = (cityNameInput) => { | ||
| setCityName(cityNameInput.toLowerCase().trim()) | ||
| } | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityNameInput}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| return | ||
| } | ||
| useEffect(() => { | ||
| if (!cityName) return | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
| let ignore = false | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| const formattedWeather = { | ||
| ...weatherData, | ||
| city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| const startFetching = async () => { | ||
| const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| if (!coordinates) { | ||
| setErrorMessage(`City "${cityName}" not found. Try London, Delhi, or Tokyo.`) | ||
| setCurrentWeather(null) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(true) | ||
| setErrorMessage(null) | ||
|
|
||
| try { | ||
| const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
|
|
||
| if (!ignore) { | ||
| setCurrentWeather({ | ||
| ...weatherData, | ||
| city: cityName.charAt(0).toUpperCase() + cityName.slice(1) | ||
| }) | ||
| } | ||
| } catch (err) { | ||
| if (!ignore) { | ||
| setErrorMessage(err.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| } | ||
| } finally { | ||
| if (!ignore) setIsLoading(false) | ||
| } | ||
| setCurrentWeather(formattedWeather) | ||
| setWeatherReports(formattedWeather) | ||
| } catch (fetchError) { | ||
| setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| setCurrentWeather(null) | ||
| setWeatherReports(null) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| const handleThemeToggle = () => { | ||
| setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| } | ||
| startFetching() | ||
|
|
||
| return () => { | ||
| ignore = true | ||
| } | ||
| }, [cityName]) | ||
|
|
||
| return ( | ||
| <div className={`${styles.appContainer} ${styles[theme]}`}> | ||
| <div className={styles.appContainer}> | ||
| <div className={styles.mainWrapper}> | ||
| <Header theme={theme} onThemeToggle={handleThemeToggle} /> | ||
| <Header /> | ||
|
|
||
| <main className={styles.mainSection}> | ||
| <SearchForm onSearch={handleSearch} theme={theme} /> | ||
| <SearchForm onSearch={handleSearch} /> | ||
| {isLoading && <p className={styles.statusText}>Searching the skies...</p>} | ||
| {errorMessage && <div className={styles.errorBox}>{errorMessage}</div>} | ||
| {currentWeather && !isLoading && <WeatherCard data={currentWeather} theme={theme} />} | ||
| {currentWeather && !isLoading && <WeatherCard data={currentWeather} />} | ||
| </main> | ||
| <footer className={`${styles.footer} ${styles[theme]}`}> | ||
| Powered by Open-Meteo API | ||
| </footer> | ||
|
|
||
| <footer className={styles.footer}>Powered by Open-Meteo API</footer> | ||
| </div> |
There was a problem hiding this comment.
Severity: 🟠 Major
Architecture: Theme removal makes this PR do more than “fetch cleanup logic”
This PR removes theme state/styling/props while also introducing new fetch orchestration. That couples two unrelated concerns, making regressions harder to attribute and review.
Why this matters: Keeping changes focused reduces the chance of accidental UI behavior changes and makes rollback/diagnosis much easier.
Suggested fix: revert theme-related changes in this PR (keep theme behavior identical), and ship theme removal (if intended) as a separate PR with its own test updates and component adjustments.
| // // src/App.jsx | ||
| // import { useState } from 'react' | ||
| // import Header from './components/Header.jsx' | ||
| // import { SearchForm } from './components/SearchForm.jsx' | ||
| // import WeatherCard from './components/WeatherCard.jsx' | ||
| // import { fetchWeatherInfoByCoordinates, CITY_COORDS } from './services/weatherApi' | ||
| // import styles from './App.module.css' | ||
|
|
||
| // function App() { | ||
| // const [currentWeather, setCurrentWeather] = useState(null) | ||
| // const [weatherReports, setWeatherReports] = useState(null) | ||
| // const [isLoading, setIsLoading] = useState(false) | ||
| // const [errorMessage, setErrorMessage] = useState(null) | ||
| // const [theme, setTheme] = useState('dark') | ||
|
|
||
| // const handleSearch = async (cityNameInput) => { | ||
| // const cityName = cityNameInput.toLowerCase().trim() | ||
| // const coordinates = CITY_COORDS[cityName] | ||
|
|
||
| // if (!coordinates) { | ||
| // setErrorMessage(`City "${cityNameInput}" not found. Try London, Delhi, or Tokyo.`) | ||
| // setCurrentWeather(null) | ||
| // setWeatherReports(null) | ||
| // return | ||
| // } | ||
|
|
||
| // setIsLoading(true) | ||
| // setErrorMessage(null) | ||
|
|
||
| // try { | ||
| // const weatherData = await fetchWeatherInfoByCoordinates(coordinates.lat, coordinates.lon) | ||
| // const formattedWeather = { | ||
| // ...weatherData, | ||
| // city: cityNameInput.charAt(0).toUpperCase() + cityNameInput.slice(1) | ||
| // } | ||
| // setCurrentWeather(formattedWeather) | ||
| // setWeatherReports(formattedWeather) | ||
| // } catch (fetchError) { | ||
| // setErrorMessage(fetchError.message || 'Failed to fetch weather data.') | ||
| // setCurrentWeather(null) | ||
| // setWeatherReports(null) | ||
| // } finally { | ||
| // setIsLoading(false) | ||
| // } | ||
| // } | ||
|
|
||
| // const handleThemeToggle = () => { | ||
| // setTheme((previousTheme) => (previousTheme === 'dark' ? 'light' : 'dark')) | ||
| // } | ||
|
|
||
| // return ( | ||
| // <div className={`${styles.appContainer} ${styles[theme]}`}> | ||
| // <div className={styles.mainWrapper}> | ||
| // <Header theme={theme} onThemeToggle={handleThemeToggle} /> | ||
| // <main className={styles.mainSection}> | ||
| // <SearchForm onSearch={handleSearch} theme={theme} /> | ||
| // {isLoading && <p className={styles.statusText}>Searching the skies...</p>} | ||
| // {errorMessage && <div className={styles.errorBox}>{errorMessage}</div>} | ||
| // {currentWeather && !isLoading && <WeatherCard data={currentWeather} theme={theme} />} | ||
| // </main> | ||
| // <footer className={`${styles.footer} ${styles[theme]}`}> | ||
| // Powered by Open-Meteo API | ||
| // </footer> | ||
| // </div> | ||
| // </div> | ||
| // ) | ||
| // } | ||
|
|
||
| // export default App | ||
|
|
There was a problem hiding this comment.
Severity: 🟡 Moderate
Clarity: Large commented-out prior implementation should be removed
The file contains ~70 lines of commented-out old code.
Why this matters: It makes review harder, increases merge-conflict risk, and encourages accidental reintroduction. Git history already preserves the old version.
Suggested fix: delete the commented-out block (lines 1–70) entirely. If you need to preserve context, link to the PR/commit in the PR description instead.
PR AnalysisFocus Areas for Architect Review
RecommendationAction: REQUEST CHANGES Quick Wins:
Author InsightsPR Type: feat Missing Skills
Strengths
|
What This PR Does
App.jsxfor the Weather API fetch usinguseEffect.[cityName]to run the effect only when the searched city changes.Manual Testing Steps (Reviewer)
npm install&npm run devto start the app.Fetching weather…andWeather fetched:with the correct data.Files Changed
src/App.jsxandsrc/test/App.test.jsx→ Added cleanup logic usinguseEffectwith ignore flag and[cityName]dependency.✅ Definition of Done
Code Quality
Testing