reactjstypescriptnext.jshtml-inputurlsearchparams

How can I make OnChange and OnKeyDown work well together?


So I am making a weather app that connects to OpenWeather api and want certain functionality with the search input element I'm using. I want to get the user input after the user presses the enter key. So I use OnChange event to get that value and I also have OnKeyDown event to call my searchLocation function which updates the current link to have the user input which is the location for the api to show the weather for. What I'm trying to do with that is to get the user input from that updated link. I'm not sure how to get that.

What keeps happening is that I press enter but the location doesn't update. I take out certain logic but when I do it keeps trying to call the api. So I felt it necessary to set a boolean variable to true when enter key is pressed, but that doesn't quite work so I'm lost on how to get this to work.

Also I understand that there are better ways to go about this but for now I would just like to keep both the onChange and OnKeyDown events.

.tsx with most logic

  const searchParams = useSearchParams();
  const pathName = usePathname();
  const { replace } = useRouter();
  globalThis.keyPressed = false
  let location = '';


function setLocation(lct: string, evt: ChangeEvent<HTMLInputElement>){
    
    evt.preventDefault();
    
    if(globalThis.keyPressed){ 
      const selectedLocation = searchParams.get("location");
      
    replace(`?${new URLSearchParams({location: lct})}`)

     ApiHandler(location);
    }
}

function searchLocation(evt: React.KeyboardEvent<HTMLInputElement>){
   evt.preventDefault();


        
        globalThis.keyPressed = true;

        
        const params = new URLSearchParams(searchParams);

        if (location != ''){
            params.set('query', location);


        } else{
            params.delete('query');
        }
        replace(`${pathName}?${params.toString()}`);
      }

//the input element in question
   <input
       value={typeof location === 'string' ? location : ''}
       
       onChange={event => {setLocation(event.target.value, event)}}
               
      onKeyDown={ event => {event.key === 'Enter' ? searchLocation(event) : null} }
      
        placeholder="Enter Location"
        type="text"
        defaultValue={searchParams.get('query')?.toString()}
      /> 


.jsx for api handling



export default async function ApiHandler(location) {
  
  try{
       

    const data = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=imperial&appid=${process.env.API_KEY}`
      );
  
    const res = await data.json();

    
    console.log(res.data);
    
      } catch (error){
          console.log("Error connecting to api");
          return new Response("Error in getting weather data", { status: 500 });
      }

}

.d.ts for global variable

declare global {
    var keyPressed: boolean
}

Solution

  • Use native "input" element, wrap it into "form" element. Use only "onChange" callback, use debounce, if necessary. Form element provides you handler "onEnter" for free. On "onSubmit" callback, you can play with searchParams.

    And be careful with evt.preventDefault(), you can confuse yourself.

    Anyway, there are many variants to do your task, it's up to you :)