I am trying to learn React and Typescript. I am building a little demo app that sends a request to the api https://api.zippopotam.us/us to get postcode information and display the place information. If the request is submitted with an invalid postcode I want to display an error message.
Below is my code. I have the call to the api inside the useEffect method and notice that this runs twice when the app is loaded i.e. before the user has entered a zipcode and clicked the search button so the api call is made without the zipcode and hence returns a 404, so the error code is always displayed on initial load.
I thought that the useEffect method should only get run when the zipSearch value changes i.e. when a user enters a zip and clicks enter. Although reading up on the useEffect method is seems it runs everytime the app component renders. Im also a little confused why it runs twice on initial load.
How can I get this to work the way I want it to? Any help would be much appreciated. If a moderator deletes this question, can they please let me know why? Thanks.
import React, {FormEvent, useEffect, useState} from "react";
import { useForm } from 'react-hook-form'
import "./App.css";
import axios from "axios";
import { IPlace } from "./IPlace";
export default function App2(){
const [placeFound, setPlaceFound] = useState<IPlace[]>([]);
const [zipSearch, setZipSearch] = useState("");
const [errorFound, setErrorFound] = React.useState("");
const renderPlaces = () => {
console.log("Render places runs")
if(placeFound.length !== 0){
return (<div className="table-container">
<table>
<thead>
<tr>
<th><span>State</span></th>
<th><span>Longitude</span></th>
<th><span>Latitude</span></th>
<th><span>Place Name</span></th>
</tr>
</thead>
{placeFound.map((place) =>{
return (
<tbody>
<tr>
<td>{place.state}</td>
<td>{place.longitude}</td>
<td>{place.latitude}</td>
<td>{place["place name"]}</td>
</tr>
</tbody>
)})}
</table>
</div>)
}
}
React.useEffect(() => {
console.log("useEffect is run")
const query = encodeURIComponent(zipSearch);
axios
.get(`https://api.zippopotam.us/us/${query}`,{
})
.then((response) => {
setPlaceFound(response.data.places);
setErrorFound("");
})
.catch((ex) => {
let errorFound = axios.isCancel(ex)
? 'Request Cancelled'
: ex.code === 'ECONNABORTED'
? 'A timeout has occurred'
: ex.response.status === 404
? 'Resource Not Found'
: 'An unexpected error has occurred';
setErrorFound(ex.code);
setPlaceFound([]);
});
}
},[zipSearch]);
const search=(event: FormEvent<HTMLFormElement>) =>{
console.log("Search method runs")
event.preventDefault();
const form = event.target as HTMLFormElement;
const input = form.querySelector('#zipSearchInput') as HTMLInputElement;
setZipSearch(input.value);
}
return (
<div className="App">
<div className="search-container">
<h1>Place Search using Zip Code</h1>
<form className="searchForm" onSubmit={event => search(event)}>
<div>
<label htmlFor="zipSearchInput">Zip Code</label>
<input {...register('zipSearchInput', { required: true, minLength: 5, maxLength: 5 }) }
id="zipSearchInput"
name="zipSearchInput"
type="text"
/>
</div>
{
errors.zipSearchInput && <div className="error">Zip Code is required and must be 5 digits long</div>
}
<button type="submit">Search</button>
</form>
</div>
{placeFound.length !== 0 && renderPlaces()}
{errorFound !== "" && <p className="error">{errorFound}</p>}
</div>)
}
