I have a weather application with two components where a user type city name etc if it is correct then data is fetched by Api and rendered on the browser but if the user type wrong input like (city name in my case), the whole page stops rendering when I click submit button OR Press enter key(onSubmit).even when the search bar is empty and onClick or onSubmit event trigger, the page gone blank(stops rendering). I want that when the user types the wrong city name or the search bar is empty, the page renders the default weather data or stop fetching the city data on the wrong user input. Any solution? App.js Comp
import React, {useState} from "react";
import "./App.css";
import Maindata from "./Components/Maindata";
import Search from "./Components/Search";
// import Time from "./Components/Time";
function App() {
const [location, setLocation] = useState();
return (
<div className="mainpage">
<div className="searchComp">
<Search {...{ location, setLocation }} />
</div>
<div className="details">
<Maindata city={location} />
</div>
</div>
);
}
export default App;
Comp 1 Search.js
import React, { useState } from "react";
import "../Componentstyle/search.css";
export default function Search({ setLocation }) {
const [city, setCity] = useState("");
const handlesubmit = (e) => {
e.preventDefault();
setLocation(city);
};
return (
<div className="main">
<nav className="istclass">
<form className="form" onSubmit={handlesubmit}>
<div className="search">
<input
value={city}
placeholder="search city"
className="searchbox"
onChange={(e) => setCity(e.target.value)}
/>
<button className="nd" type="button" onClick={handlesubmit}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
Comp2 Maindata.js
import React, { useState, useEffect } from "react";
import "../Componentstyle/Main.css";
export default function Maindata({ city = "mansehra" }) {
const [data, setData] = useState();
const weather = async (city) => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => setData(actualData));
};
useEffect(() => {
weather(city);
}, [city]);
if (!data) {
return <div>Loading...</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<>
<div className="maindata">
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp.toFixed()} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like.toFixed()} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
</>
);
}
CodePudding user response:
If the user does not enter any city before submitting, or if the user enters a city that the API does not recognize, openweathermap will return the following response:
{cod: '400', message: 'Nothing to geocode'}
Which doesn't have a weather
property - so although the data
state exists in the Maindata
component, doing
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
will fail, throwing a runtime error and preventing rendering.
You need not only
if (!data) {
return <div>Loading...</div>;
}
but also something like
if (!data.weather) {
return <div>City "{city}" not recognized</div>;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
const [location, setLocation] = useState();
return (
<div className="mainpage">
<div className="searchComp">
<Search {...{ location, setLocation }} />
</div>
<div className="details">
<Maindata city={location} />
</div>
</div>
);
}
function Search({ setLocation }) {
const [city, setCity] = useState("");
const handlesubmit = (e) => {
e.preventDefault();
setLocation(city);
};
return (
<div className="main">
<nav className="istclass">
<form className="form" onSubmit={handlesubmit}>
<div className="search">
<input
value={city}
placeholder="search city"
className="searchbox"
onChange={(e) => setCity(e.target.value)}
/>
<button className="nd" type="button" onClick={handlesubmit}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
function Maindata({ city = "mansehra" }) {
const [data, setData] = useState();
const weather = async (city) => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => setData(actualData));
};
useEffect(() => {
weather(city);
}, [city]);
if (!data) {
return <div>Loading...</div>;
}
if (!data.weather) {
return <div>City "{city}" not recognized</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<>
<div className="maindata">
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp.toFixed()} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like.toFixed()} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
</>
);
}
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
</script>
If you want to preserve the existing rendered map data if the input is invalid, then add another state that keeps track of whether the entered city is valid or not, and use that instead of overwriting the API response state if invalid.
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
const [location, setLocation] = useState();
return (
<div className="mainpage">
<div className="searchComp">
<Search {...{ location, setLocation }} />
</div>
<div className="details">
<Maindata city={location} />
</div>
</div>
);
}
function Search({ setLocation }) {
const [city, setCity] = useState("");
const handlesubmit = (e) => {
e.preventDefault();
setLocation(city);
};
return (
<div className="main">
<nav className="istclass">
<form className="form" onSubmit={handlesubmit}>
<div className="search">
<input
value={city}
placeholder="search city"
className="searchbox"
onChange={(e) => setCity(e.target.value)}
/>
<button className="nd" type="button" onClick={handlesubmit}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
function Maindata({ city = "mansehra" }) {
const [data, setData] = useState();
const [cityValid, setCityValid] = useState(false);
const weather = async (city) => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => {
if (actualData.weather) {
setCityValid(true);
setData(actualData)
} else {
setCityValid(false);
}
});
};
useEffect(() => {
weather(city);
}, [city]);
if (!data) {
return <div>Loading...</div>;
}
if (!data.weather) {
return <div>City "{city}" not recognized</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<>
<div className="maindata">
{!cityValid && <div>City "{city}" not found</div>}
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp.toFixed()} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like.toFixed()} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
</>
);
}
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
</script>