0

I'm working on an app that will help me fetch a five day weather forecast for any city using openweathermap.org, However, i get the above error in the browser whenever i call a function renderWeather in the WeatherList container as shown in my code snippets below.

This file is the index.js where i imported redux promise

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';

import App from './components/app';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <App />
  </Provider>
  , document.querySelector('.container'));

Code for the weather_list container is below

import React, {Component} from 'react';
import {connect} from 'react-redux';

class WeatherList extends Component{

renderWeather(cityData){
  return(
    <tr>
      <td>{cityData.city.name}</td>
    </tr>
  );

}

  render(){
    return(
      <table className="table table-hover">
        <thead>
          <tr>
          <th>City</th>
          <th>Temperature</th>
          <th>Pressure</th>
          <th>Humidity</th>
          </tr>
        </thead>
        <tbody>
        {this.props.weather.map(this.renderWeather)}
        </tbody>
      </table>
    );
  }
}
function mapStateToProps(state){
  return {
    weather: state.weather
  };
}
export default connect(mapStateToProps)(WeatherList);

Code for the search_bar container is below

import React, {Component}from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchWeather} from '../actions/index';

class SearchBar extends Component{
  constructor(props){
    super(props);

    this.state = {term: ''};
    this.onInputChange = this.onInputChange.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
  }

onInputChange(event){
  console.log(event.target.value);
  this.setState({term: event.target.value});
}

onFormSubmit(event){
  event.preventDefault();
  // we need to go and fetch weather data!
  this.props.fetchWeather(this.state.term);
  this.setState({term: ''});
}

  render(){
    return(
      <form onSubmit={this.onFormSubmit} className="input-group">
        <input
          placeholder="Get a five-day forecast in your favorite cities"
          className="form-control"
          value={this.state.term}
          onChange={this.onInputChange} />
        <span className="input-group-btn">
          <button type="submit" className="btn btn-secondary">Submit</button>
        </span>
      </form>
    );
  }
}
function mapDispatchToProps(dispatch){
  return bindActionCreators({fetchWeather:fetchWeather}, dispatch)
}

export default connect(null, mapDispatchToProps)(SearchBar);

Code for the app component is shown below

import React, { Component } from 'react';
import SearchBar from '../containers/search_bar.js';
import WeatherList from '../containers/weather_list.js';


export default class App extends Component {
  render() {
    return (
      <div>
        <SearchBar />
        <WeatherList />
      </div>
    );
  }
}

Code for the weather reducer is shown below

import {FETCH_WEATHER} from '../actions/index';

export default function(state = [], action){
  switch (action.type) {
    case FETCH_WEATHER:
      //return state.concat([action.payload.data]);
      return ([action.payload.data, ...state])
  }
  return state;
}

Code for index.js in the reducers folder is shown below

import { combineReducers } from 'redux';
import WeatherReducer from './reducer_weather';

const rootReducer = combineReducers({
  weather: WeatherReducer
});

export default rootReducer;

Code for the action creator in the actions folder is shown below

import axios from 'axios';
const API_KEY = '03d17752ca362bc60ca7df94aac228a6';
const ROOT_URL =`https://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;

export const FETCH_WEATHER = 'FETCH_WEATHER';

export function fetchWeather(city){
  const url = `${ROOT_URL}&q=${city},us`;
  const request = axios.get(url);

  console.log('Request:', request);

  return{
    type: FETCH_WEATHER,
    payload: request
  }
}

Finally, an image showing the error in the broswer is below. enter image description here

Any assistance on how to go about the error is much appreciated

Patrick Burtchaell
  • 4,112
  • 1
  • 14
  • 25
lasabahebwa
  • 281
  • 2
  • 14
  • In WeatherList you can try `{this.props.weater && this.props.weather.map(..` your code this will not cause to execute code when you have `undefined` variable – Navin Gelot Nov 03 '18 at 09:55

2 Answers2

1

Initially this.props.weather will be undefined so you need to do conditional check before doing .map like in two ways

this.props.weather && this.props.weather.map

OR

Array.isArray(this.props.weather) && this.props.weather.map

Also when doing .map you need to pass weather data to renderWeather method because it expects data but you are not passing the data to it in .map

Change

{this.props.weather.map(this.renderWeather)}

To

 {Array.isArray(this.props.weather) && this.props.weather.map(item => this.renderWeather(item))}

Also set unique key to tr element here

renderWeather(cityData){
  return(
    <tr key={cityData.city && cityData.city.id}> //if you do not have unique id then use index as key
      <td>{cityData.city && cityData.city.name}</td>
    </tr>
  ); 
}

or if your data doesn't contain unique id then use index as key

 {Array.isArray(this.props.weather) && this.props.weather.map((item, index) => this.renderWeather(item, index))}

Also set unique key to tr element here

renderWeather(cityData, index){
  return(
    <tr key={"city-"+index}> //if you do not have unique id then use index as key
      <td>{cityData.city && cityData.city.name}</td>
    </tr>
  ); 
}
Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
0

It was because this.props.weather is undefined thus calling the .map() function on an undefined will throw you the error. You can add the validation ahead to make sure it only call the .map() when it is an array (when the data comes in).

You can add:

Array.isArray(this.props.weather) && ...

Do the changes here:

import React, {Component} from 'react';
import {connect} from 'react-redux';

class WeatherList extends Component{

renderWeather(cityData){
  return(
    <tr>
      <td>{cityData.city.name}</td>
    </tr>
  );

}

  render(){
    return(
      <table className="table table-hover">
        <thead>
          <tr>
          <th>City</th>
          <th>Temperature</th>
          <th>Pressure</th>
          <th>Humidity</th>
          </tr>
        </thead>
        <tbody>
        {Array.isArray(this.props.weather) && this.props.weather.map(this.renderWeather)}
        </tbody>
      </table>
    );
  }
}
function mapStateToProps(state){
  return {
    weather: state.weather
  };
}
export default connect(mapStateToProps)(WeatherList);
Jee Mok
  • 6,157
  • 8
  • 47
  • 80