'React | Lifting the state up with event parameter gives error
So I've been trying to pass through the result of an API call, but in the function that's returning the result I have a check to see if Enter was pressed or a click on the button. Now when I pass this function through while lifting the state up I get this error:
react-dom.development.js:11996 Uncaught TypeError: Cannot read properties of null (reading 'key')
at searchCity (Input.js:10:1)
at basicStateReducer (react-dom.development.js:16376:1)
at updateReducer (react-dom.development.js:16500:1)
at updateState (react-dom.development.js:16836:1)
at Object.useState (react-dom.development.js:17762:1)
at useState (react.development.js:1617:1)
at Weather (Weather.js:6:1)
at renderWithHooks (react-dom.development.js:16141:1)
at updateFunctionComponent (react-dom.development.js:20313:1)
at beginWork (react-dom.development.js:22356:1)
Anyone have any idea on how to fix this?
Here's my code: Input.js (child)
import { useRef } from "react";
import Data from '../config'
const Input = (props) => {
const userInput = useRef();
function searchCity(e) {
if (e.key === 'Enter' || e.type === "click") {
const inputValue = userInput.current.value;
console.log(inputValue)
fetch(Data.baseUrl1 + inputValue)
.then ((response) => response.json())
.then (data => {
const cityData = data.data[0]
const lat = data.data[0].latitude
const lon = data.data[0].longitude
console.log(cityData, lat, lon)
getWeatherData(lat, lon, cityData)
})
}
}
function getWeatherData(latitude, longitude, geoData) {
fetch(Data.baseUrl2 + latitude
+ "&lon=" + longitude + "&appid=" + Data.key
+ "&exclude=current,minutely,hourly&units=metric")
.then((response) => response.json())
.then(data => {
return data;
})
}
return (
<div className="inputfield">
<input ref={userInput} onKeyDown={searchCity} type="text" placeholder="City name here..."/>
<button onClick={() => props.updateFetch(searchCity)}>Search</button>
</div>
)
}
export default Input;
Weather.js (parent)
import { useState } from "react";
import Input from "../components/Input";
const Weather = () => {
const [fetchedData, setFetchedData] = useState(null);
return (
<Input updateFetch={fetchdata => setFetchedData(fetchdata)}/>
)
}
export default Weather;
Solution 1:[1]
I believe you encounter this issue when you press the search button, which has problematic onClick behavior. When the search button is clicked, searchCity, which requires an e event parameter, immediately gets called, but you do not pass it in, so e is null, and you get the error Cannot read properties of null. You need to revise your onClick function to pass in the event: onClick={(e) => props.updateFetch(searchCity(e))}.
Solution 2:[2]
Check this line -
Can not read property of null, reading key.
Which means e is your object but e.key is nothing and you are trying to read e.Key also means key is not the direct property of e object.
I guess it should be e.target.key
You can console log e and find all the properties it has and then filter out where the key property is.
The same goes for type.
Solution 3:[3]
tangled concerns
There's a lot of issues with the presented code. There is no return before getWeatherData so the .then returns undefined. Not that it really matters because your component has nowhere to store the city and weather data once it is fetched.
However, the biggest issue is that you are tangling API logic directly in your components. By isolating this code in separate modules, you make the components much easier to read/write and the API functions become reusable in other areas of your program. Note use of URL and URLSearchParams to avoid constructing URLs using strings and properly encode URL subcomponents -
// api/city.js
const BASE_URL = "https://api.citydata.com"
async function getCity(query) {
const u = new URL("/get", BASE_URL)
u.searchParams = new URLSearchParamas({ query })
// https://api.citydata.com/get?query=foobar
const {data} = await fetch(u).then(r => r.json())
return data[0]
}
export { getCity }
// api/weather.js
const BASE_URL = "http://weatherdata.org"
const APP_ID = "2c50fb2b94ae720g"
async function getWeather(lat, lon) {
const u = new URL("/json", BASE_URL)
u.searchParams = new URLSearchParams({
lat: lat,
lon: lon,
appid: APP_ID,
exclude: "current,minutely,hourly",
units: "metric"
})
// http://weatherdata.org/json?lat=1.23&lng=4.56&appid=2c50fb2b94ae720g&exclude=current,minutely,hourly&units=metric
return fetch(u).then(r => r.json())
}
export { getWeather }
controlled components
Next I'd say there's some issues with your Search component. For one, there's no need to useRef in this situation. onChange should be used to make your input a controlled component and onKeyDown can be used to detect e.key == "Enter". Here's a functioning demo that you can run here in the browser to see how it's going to work -
// components/search.js
function Search() {
const [query, setQuery] = React.useState("")
const onSearch = e => {
console.log("search for:", query)
}
return <div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key == "Enter" && onSearch(e)}
placeholder="enter a city name..."
/>
<button type="button" onClick={onSearch} children="?" />
</div>
}
ReactDOM.render(<Search />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
bring it together
Now that the simplified Search component is working and the api/city and api/weather modules are finished, let's tie it all together in onSearch. Note onSearch is dramatically simplified thanks to the API modules doing the heavy lifting -
// components/search.js
import { getCity } from "../api/city.js"
import { getWeather } from "../api/weather.js"
function Search() {
const [query, setQuery] = React.useState("")
const [city, setCity] = React.useState(null)
const [weather, setWeather] = React.useState(null)
const onSearch = async e => {
try {
const city = await getCity(query)
const weather = await getWeather(city.latitude, city.longitude)
setCity(city)
setWeather(weather)
}
catch (err) {
console.error(err.message)
}
}
return <div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key == "Enter" && onSearch(e)}
placeholder="enter a city name..."
/>
<button type="button" onClick={onSearch} children="?" />
{ city && weather
? [ <City city={city} />, <Weather weather={weather} /> ]
: null
}
</div>
}
Lastly define City and Weather components for when city and weather state is loaded -
function City({ city }) {
return <div>{city.name} {city.state} {city.timezone} ...</div>
}
function Weather({ weather }) {
return <div>{weather.temp} {weather.timeOfDay} ...</div>
}
lift state up
If you want to lift this state above Search into a higher component, I would suggest moving city, weather and onSearch up to App and pass only onSearch down to the Search component. Note the updated onSearch receives a query string instead of an Event -
// components/app.js
import { getCity } from "../api/city.js"
import { getWeather } from "../api/weather.js"
import Search from "./search.js"
function App({
const [city, setCity] = React.useState(null)
const [weather, setWeather] = React.useState(null)
const onSearch = async query => {
try {
const city = await getCity(query)
const weather = await getWeather(city.latitude, city.longitude)
setCity(city)
setWeather(weather)
}
catch (err) {
console.error(err.message)
}
}
return <div>
<Search onSearch={onSearch} />
{ city && weather
? [ <City city={city} />, <Weather weather={weather} /> ]
: null
}
</div>
}
Note the minor difference is how onSearch is called in the updated Search component -
// components/search.js
function Search({ onSearch }) {
const [query, setQuery] = React.useState("")
return <div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key == "Enter" && onSearch(query)}
placeholder="enter a city name..."
/>
<button type="button" onClick={e => onSearch(query)} children="?" />
</div>
}
PS never write .then(data => data). It does absolutely nothing.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | gloo |
| Solution 2 | Ranu Vijay |
| Solution 3 |
