Wojciech Wojas
Wojciech Wojas

Reputation: 75

How do I access JSON file after filtering and pushing into array

I have a massive problem with my weather app.

I'm using the OpenweatherMap free API and I created a page with two select inputs: one for choosing a country from an array of objects stated directly within the state and second select for choosing a city.

The second select is tricky since I uploaded a huge JSON file containing a list of all cities served by the API and I want to filter out every city which does not belong to the previously chosen country.

My issue is that the generateCitiesList() function doesn't pass filtered elements into the this.state.cityList

 import React from "react";
import ReactDOM from "react-dom";
import CITIES from "./cities.json";
import COUNTRIES from "./countries.json";
import "./styles.css";


   // So getCitiesByCountry accepts (country) parameter for which we use 
   // this.state.selectedCountry and filter how exactly?

   // Do we compare every element within cities.json file so that it returns
   // an object (array?) that meets the criteria of element.country being equal to
   // this.state.selectedCountry?

const getCitiesByCountry = (country) =>
  CITIES.filter(city => city.country === country);

// 

class App extends React.PureComponent {
  state = { selectedCoutnry: "" };

  handleOnSelectCountry = e => {
    this.setState({ selectedCoutnry: e.target.value });
  };

  render() {


      // What does this line of code do? It looks like an object destructurization
      // so should I assume that from now on
      // const selectedCountry is equal to this.state.selectedCountry ?

    const { selectedCoutnry } = this.state;


    return (
      <div className="App">
        <select value={selectedCoutnry} onChange={this.handleOnSelectCountry}>
          <option>Select a Country</option>

          // That makes a lot of sense. Why would I store countries list in state 
          while I could access it via JSON file.

          {COUNTRIES.map(({ name, id }) => (
            <option value={id} key={id}>
              {name}
            </option>
          ))}
        </select>
        {selectedCoutnry && (
          <select>
            <option>Select a City</option>

            // So we use a function getCitiesByCountry which returns a filtered object 
           //  which is then mapped through to render <option>s?

            {getCitiesByCountry(selectedCoutnry).map(({ name, id }) => (
              <option value={id} key={id}>
                {name}
              </option>
            ))}
          </select>
        )}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

[ { "id": 707860, "name": "Hurzuf", "country": "UA", "coord": { "lon": 34.283333, "lat": 44.549999 } }, { "id": 519188, "name": "Novinki", "country": "RU", "coord": { "lon": 37.666668, "lat": 55.683334 } }, { "id": 1283378, "name": "Gorkhā", "country": "NP", "coord": { "lon": 84.633331, "lat": 28 } } ]


Upvotes: 1

Views: 485

Answers (4)

Wojciech Wojas
Wojciech Wojas

Reputation: 75

Ok, thanks to @Yoav Kadosh (kudos!) I have the answear below.

It turns out that in order to effectively map through a selected number of items you should:

  1. Create a function which returns filtered object

Instead, you can also apply chain operators such as variable.filter( (parameter) => parameter > 2).map({element} => {element} ).

  1. Map over the results of the above function and push them into DOM
   constructor(props) {
       super(props);
       this.state = {
            country: '',
            city: '',
            result: {
               temperature: ''
            }
       }
   }


    handleCountrySelect = (e) => {
           this.setState({
               country: e.target.value
           });
           console.log(e.target.value);

    };


    filterCitiesByCountry = (country) => {
        return citiesJSON.filter ( city => city.country === country)

    };


    handleCitySelect = (e) => {
        this.setState ({
            city: e.target.value
        });

        console.log(e.target.value)

    };


    getWeather = async (e) => {
        e.preventDefault();

        fetch(`http://api.openweathermap.org/data/2.5/weather?q=${this.state.city},${this.state.country}&appid=${APIKEY}`)
            .then( res => res.json())
            .then( res =>
                this.setState ({
                    result: {
                        temperature: Math.round(res.main.temp - 273.15)
                    }
                })

            )
    };




    render() {

        return (

            <main>

                <section className='row text-center' id='weather_select_section'>
                <form>

                    <div className="countrySelect col-12 col-sm-6 text-center mt-5 mb-5 ">
                        <label> Select Country </label>

                        <select onChange={this.handleCountrySelect} required placeholder='Country...'>

                        <option value='' data-index='Country...'>  </option>

                        { countriesJSON.map(({name, code, key} ) => {
                            return (
                            <option value={code} key={key} > {name} </option>

                            )
                        })}
                    </select>
                    </div>


                    <div className="citySelect col-12 col-sm-6 text-center mt-5 mb-5 ">
                        <label> Select City </label>

                        {this.state.country && (
                            <select onChange={this.handleCitySelect} placeholder='City...' required>
                                <option value='' data-index='City...'></option>


                                { this.filterCitiesByCountry(this.state.country).map(({name, key}) =>
                                    <option value={name} key={key}> {name} </option>

                                )}

                            </select>
                        )

                        }

                    </div>

                    <button type='submit' onClick = {this.getWeather} className=" col-10 mt-5 mb-5 text-center "> Get Weather</button>


                </form>


                <div className=" col-12 WeatherInfoBox ">

                    <h3> Weather in {this.state.city}</h3>
                    <p> {this.state.result.temperature} &#8451;</p>

Upvotes: 0

Yoav Kadosh
Yoav Kadosh

Reputation: 5155

Your code didn't work because of this:

citiesArray.push(filterCities)

which should've been written as:

citiesArray.push(...filterCities)

But you can avoid all that by reducing your code to:

generateCitiesList = () => { 
    this.setState ({
        listCity: citiesList.filter(({country}) => (
            country === `${this.state.country}`;
        ))
    });
};

However, you should only use the state for storing the minimum data that affects the view, in your case - the selected country/city.

The list of cities for the currently selected country should not be part of the state since it can be derived from the selected country. Simply write a function that takes a country and return the list of cities for that country, and run it in the render() against the selected country:

render() {
    return (
        ...
        <select>
            {getCitiesByCountry(this.state.country).map(city => (
                <option value={city}>{city}</option>
            ))}
        </select>
        ...
    );
}

Edit vigorous-cdn-3kk92

Upvotes: 0

Gh05d
Gh05d

Reputation: 8982

This looks evil. Don't mutate the state directly.

   generateCitiesList = () => {
        const self = this;    
           self.state.cityList.push(citiesList.filter( function (countries) {

                return countries.country === `${self.state.country}`
            }));

        console.log(this.state.cityList);

        this.showCitiesList()  ===> WORKS, pushing desired objects into an array 
        };

Populate the state instead in the lifecycle function componentDidMount

componentDidMount() {
  const cityList = citiesList.filter(element => element.country === this.state.country);

  this.setState({cityList});
}

Upvotes: 0

cbdeveloper
cbdeveloper

Reputation: 31485

See if that works for you.

This is representing your JSON with the cities.

cities: {
  city1: {
    cityName: 'City1',
    country: 'A'
  },
  city2: {
    cityName: 'City2',
    country: 'A'
  },
  city3: {
    cityName: 'City3',
    country: 'B'
  },
  city4: {
    cityName: 'City4',
    country: 'B'
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      selectedCountry: 'A',
      countries: ['A','B'],
      cities: {
       city1: {cityName: 'City1',
       country: 'A'},
       city2: {cityName: 'City2',
       country: 'A'},
       city3: {cityName: 'City3',
       country: 'B'},
       city4: {cityName: 'City4',
       country: 'B'},
      }
    }
    this.selectCountry = this.selectCountry.bind(this);
  }
  
  selectCountry() {
    this.setState((prevState) => {
      return({
        ...prevState,
        selectedCountry: prevState.selectedCountry === 'A' ? 'B' : 'A'
      });
    });
  }
  
  render() {
  
    let filteredCities = [];
    
    for (let i in this.state.cities) {
      this.state.selectedCountry === this.state.cities[i].country ?
        filteredCities.push(this.state.cities[i]) : null;
    }
    
    filteredCities = filteredCities.map((item)=> 
      <li>{item.cityName}</li>
    );
    
    return(
    <React.Fragment>
      <div>Selected Country: {this.state.selectedCountry}</div>
      <button onClick={this.selectCountry}>Change Country</button>
      <div>Country List</div>
      <div>
        <ul>
          {filteredCities}
        </ul>
      </div>
    </React.Fragment>
    );
  }
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 0

Related Questions