arsh coding
arsh coding

Reputation: 73

Component is not re rendering when state is changed in react

I am facing this error from 2 days straight now and i cannot fix it!!! So, I am creating a weather app in react using https://www.weatherapi.com/ this api, I used a Navbar from bootstrap and there is this search option I can use so if the user searches a particular state, it will show the data from that state, as I working am with different components to keep my App.js clean I have declared a state in App.js which I can update through the button in Navbar.js which will be passed as a prop to Display.js(which displays the data), but when I enter a state and hit submit, the page reloads(I think) and it just goes back to my original state used as a dummy. It doesn't rerender Display.js with the new data. I tried checking it by using it on the browser and it returns the response. Here's the code. App.js

import React,{useState} from 'react';
import Navbar from './Navbar.js';
import Display from './Display.js'
function App() {
  const[placeName,setPlaceName] = useState('Raipur')
  let key = 'not gonna tell';

  return (
   <>
   <Navbar setPlaceName={setPlaceName} />
   <Display key={key} placeName={placeName} />
   </>
  );
}

export default App;

Navbar.js

import React from 'react';

function Navbar(props) {
  return(
   <>
    <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
  <div className="container-fluid">
    <a className="navbar-brand" href="/">Navbar</a>
    <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="/navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span className="navbar-toggler-icon"></span>
    </button>
    <div className="collapse navbar-collapse" id="navbarSupportedContent">
      <ul className="navbar-nav me-auto mb-2 mb-lg-0">
        <li className="nav-item">
          <a className="nav-link active" aria-current="page" href="/">Home</a>
        </li>
        <li className="nav-item">
          <a className="nav-link" href="/">Link</a>
        </li>
        <li className="nav-item dropdown">
          <a className="nav-link dropdown-toggle" href="/" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul className="dropdown-menu" aria-labelledby="navbarDropdown">
            <li><a className="dropdown-item" href="/">Action</a></li>
            <li><a className="dropdown-item" href="/">Another action</a></li>
            <li><hr className="dropdown-divider"/></li>
            <li><a className="dropdown-item" href="/">Something else here</a></li>
          </ul>
        </li>
        <li className="nav-item">
          <a className="nav-link disabled">Disabled</a>
        </li>
      </ul>
      <form className="d-flex">
        <input className="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
        <button onClick={props.setPlaceName} className="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>
   </>
  );
}

export default Navbar;

Display.js

import React,{useState,useEffect} from 'react';

function Display(props) {
  
  const[weatherInfo,setWeatherInfo] = useState([]);
  
  const getWeatherInfo = async () =>{
    let url =`https://api.weatherapi.com/v1/current.json?key=${props.key}&q=${props.placeName}&aqi=no`;
    let weatherInfo = await fetch(url);
    let parsedweatherInfo = await weatherInfo.json();
    setWeatherInfo(parsedweatherInfo.location);
  }
  // eslint-disable-next-line
  useEffect(async () =>{
    getWeatherInfo();
  },[])
  return (
    <>
    <div className="container">
    <div className="row"> 
    {Object.values(weatherInfo).map((key,value)=>{
    return(
     <div className="col" key={key}>
     {key} 
     </div>
    )  
    })} 
    </div>
    </div>
    </>
  )
}

export default Display;

Example response

{
    "location": {
        "name": "London",
        "region": "City of London, Greater London",
        "country": "United Kingdom",
        "lat": 51.52,
        "lon": -0.11,
        "tz_id": "Europe/London",
        "localtime_epoch": 1631360600,
        "localtime": "2021-09-11 12:43"
    },
    "current": {
        "last_updated_epoch": 1631359800,
        "last_updated": "2021-09-11 12:30",
        "temp_c": 21.0,
        "temp_f": 69.8,
        "is_day": 1,
        "condition": {
            "text": "Partly cloudy",
            "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png",
            "code": 1003
        },
        "wind_mph": 11.9,
        "wind_kph": 19.1,
        "wind_degree": 250,
        "wind_dir": "WSW",
        "pressure_mb": 1017.0,
        "pressure_in": 30.03,
        "precip_mm": 0.0,
        "precip_in": 0.0,
        "humidity": 64,
        "cloud": 50,
        "feelslike_c": 21.0,
        "feelslike_f": 69.8,
        "vis_km": 10.0,
        "vis_miles": 6.0,
        "uv": 5.0,
        "gust_mph": 10.5,
        "gust_kph": 16.9
    }
}

Hope you can help :)

Upvotes: 3

Views: 6763

Answers (3)

GalAbra
GalAbra

Reputation: 5148

The relevant part of your code is in the Navbar component: you don't provide the new placeName to the setter function.

So, for example, your Navbar component should look somewhat like this:

function Navbar(props) {
  // This state stores the updated input value
  const [inputPlaceName, setInputPlaceName] = useState('');

  // This function provides `setPlaceName` with the input value
  function setGlobalPlaceName() {
    props.setPlaceName(inputPlaceName);
  }

  return (
    <form>
      <input type="search" onChange={setInputPlaceName} />
      <button onClick={setGlobalPlaceName} type="submit">
        Search
      </button>
    </form>
  );
}

Then, try to subscribe Display component to updates of props.placeName. This is done by adding it to the dependencies array of its useEffect:

useEffect(getWeatherInfo, [props.placeName])

Upvotes: 0

Arihant Jain
Arihant Jain

Reputation: 847

Working app https://codesandbox.io/s/jovial-pascal-kww85?file=/src/App.js

Note:

  1. Prop name cannot be key as this name is reserved to uniquely identify component.(I have used apikey as name)
    <Display  apikey={key} placeName={placeName} />
    
  2. In Navbar you have to use another state to track of input in textbox.
    const  [input, setInput] =  useState("");
    
  3. In Navbar it should be onClick={() => props.setPlaceName(input)}
    value={input}
    onChange={(e) =>  setInput(e.target.value)}
    
  4. Form element on Submit prevent Default to not refresh page.
    <form  className="d-flex"  onSubmit={(e) =>  e.preventDefault()}>
    
  5. In Display call useEffect when props.placename changes.
    useEffect(async  ()  =>  {
    
    getWeatherInfo();
    
    },  [props.placeName]);
    

Upvotes: 2

Jiř&#237; V&#237;tek
Jiř&#237; V&#237;tek

Reputation: 119

When a user clicks on the button you are not actually updating the state.

button onClick={props.setPlaceName}

should be something like

button onClick={() => props.setPlaceName("Hello world")}

Upvotes: 0

Related Questions