Reputation: 23
I am currently having trouble thinking of an efficient solution to my problem. Essentially, I have a search bar on the previous page that allows a user to enter in a city location (heropage.js). That then navigates to a concerts list page (selectconcert.js), which portrays all of the concerts from that city using the useParams() hook to take the city name from the url.
The problem I am having is the following: I want users to be able to look up a new city on the selectconcerts.js page if they would like, and clear the useParams() so it doesn't interrupt. Currently since I am using a useEffect hook with the useParams, I am sometimes getting the old location param interrupting with the new search query for city. I also feel like I am duplicating my code by having a fetchConcerts function in my useEffect hook and having a handleLocationSubmit function to process the separate queries (depending if it was searched on the prior page or current page). I dabbled into using Searchparams but please let me know if you may have a more efficient solution.
selectconcert.js (page portraying the list of concerts)
export default function Concerts() {
let navigate = useNavigate()
const [concerts, setConcerts] = useState([]);
const [city, setCity] = useState('');
const [artist, setArtist] = useState('');
const [toggled, setToggled] = useState(false);
const [invalid, setInvalid] = useState(false);
const [page, setPage] = useState(1)
let {user} = useContext(AuthContext)
const yesterday = ( d => new Date(d.setDate(d.getDate()-1)) )(new Date());
let { location } = useParams();
useEffect( () => {
const fetchConcerts = async() => {
const concertResponse = await fetch(`${process.env.REACT_APP_BUDDY_API}/api/selectconcertsforcity/${location}/&p=${page}`)
if(concertResponse.ok) {
const concertData = await concertResponse.json();
if (concertData.concerts.setlist) {
for (let i in concertData.concerts.setlist){
const dateParts = concertData.concerts.setlist[i].eventDate.split("-");
const dateObject = new Date(+dateParts[2], dateParts[1] - 1, +dateParts[0]);
concertData.concerts.setlist[i].eventDate = dateObject
}
setConcerts(concertData.concerts.setlist);
let concList = [concertData.concerts.setlist.filter(concert => ((concert.eventDate)) >= (Date.now()))]
if (concList[0].length === 0){
setConcerts(0)
}
setArtist('');
setInvalid(false)
} else {
if (location !== undefined) {
setInvalid(true)
setConcerts([])
}
}
}
}
fetchConcerts();
}, [location, page]
);
const handleLocationSubmit = async (e) => {
e.preventDefault();
const city_new = city.split(' ')
let final_city = city_new[0]
for (let i = 1; i < city_new.length; i++) {
final_city += '%20'
final_city += city_new[i]
}
const concertResponse = await fetch(`${process.env.REACT_APP_BUDDY_API}/api/selectconcertsforcity/${final_city}/&p=1`)
if(concertResponse.ok) {
const concertData = await concertResponse.json();
if (concertData.concerts.setlist) {
for (let i in concertData.concerts.setlist){
const dateParts = concertData.concerts.setlist[i].eventDate.split("-");
const dateObject = new Date(+dateParts[2], dateParts[1] - 1, +dateParts[0]);
concertData.concerts.setlist[i].eventDate = dateObject
}
setConcerts(concertData.concerts.setlist);
let concList = [concertData.concerts.setlist.filter(concert => ((concert.eventDate)) >= (Date.now()))]
if (concList[0].length === 0){
setConcerts(0)
}
setArtist('');
setInvalid(false)
} else {
console.error('concertData:', concertResponse);
setInvalid(true)
setConcerts([])
}
}
}
.....
return (
<>
<div className='selectconcerts'>
<div>
<Toggle onChange={(e) => setToggled(e.target.checked)} />
<p> Search by {toggled ? "Artist": "City "}</p>
<div className='entry'>
{ toggled ?
<form onSubmit={handleArtistSubmit}>
<input className="form-control" type="text" value={artist} required onChange={(e) => {setArtist(e.target.value)}} onKeyPress={handleKeypress}/>
</form>
:
<form onSubmit={handleLocationSubmit}>
<input className="form-control" type="text" value={city} required onChange={(e) => {setCity(e.target.value)}} onKeyPress={handleKeypress}/>
</form>
}
<div>
<p></p>
</div>
herosection.js (page with the initial city search query option)
function HeroSection() {
let {user} = useContext(AuthContext)
const [city, setCity] = useState('');
let videoimage = require('./Images/video-3.mp4')
let navigate = useNavigate()
const handleImGoingSubmit = async (e) => {
e.preventDefault();
navigate(`/selectconcerts/${city}`)
}
const handleKeypress = e => {
//it triggers enter button by pressing the enter key
if (e.keyCode === 13) {
handleImGoingSubmit();
}
};
return (
<div className='hero-container'>
<video src={videoimage} autoPlay loop muted />
<h1 align="center">ADVENTURE AWAITS</h1>
{user ? (<p align="center">Hello {user.username}, what are you waiting for?</p>):(<p align="center">What are you waiting for?</p>)}
<div className='hero-btns'>
<form onSubmit={handleImGoingSubmit}>
<input className="form-control" type="text" placeholder="Search concerts by city..." value={city} required onChange={(e) => setCity(e.target.value)} onKeyPress={handleKeypress}/>
</form>
</div>
</div>
);
}
export default HeroSection;
Upvotes: 2
Views: 405
Reputation: 202846
You've already a function fetchConcerts
and effect for fetching data based on the location. Don't duplicate this logic (DRY Principle). The handleLocationSubmit
callback should issue an imperative navigation action to the same route with the new location
path parameter.
Example:
export default function Concerts() {
const navigate = useNavigate();
...
const { location } = useParams();
useEffect(() => {
const fetchConcerts = async () => {
const concertResponse = await fetch(`${process.env.REACT_APP_BUDDY_API}/api/selectconcertsforcity/${location}/&p=${page}`);
...
};
fetchConcerts();
}, [location, page]);
const handleLocationSubmit = (e) => {
e.preventDefault();
const location = city.replaceAll(" ", "%20");
// reset back to page 1
setPage(1);
// navigate to update `location` path param and trigger rerender
navigate(`./${location}`);
};
...
Upvotes: 2
Reputation: 361
I was looking through your entire code. Including the answer to your questions, I will also suggest some improvements to other pieces of code.
For your main question, you can either add a form reset button that uses form.reset() method to get the input field back to "". But, since in your case, your input is a controlled one(uses state as its value and setState as its handler), you can do the following:
<input {...props} /> // Input for selecting city
<button onClick={() => {
setConcerts([]);
setCity("");
setArtist(" ");
setInvalid(false);
setPage(1);
// Maybe you don't want to reset the toggled state for UX purposes
}}>Reset Search</button>
The fact you mention about you repeating your code is correct, so you should extract out the input field for city, related handlers like handleLocationSubmit and the states you use with it outside of the given file, into a new component. Also, depending on how yo extract out your component, the reset form handler might have fewer setter calls. You might also have to adjust the flow of data though, which I can help you with, if you need it.
In the first few lines of the handleLocationSubmit function, you can replace the white spaces with "%20" using this:
const cityString = city.split(" ").join("%20");
Hope this helps, let me know about any doubts in the comments and I will answer them.
Upvotes: 0