I'm trying to make a forecast app with React and Flux. I fetch the data from Yahoo Weather API, and put the data to my store with a callback in jsonp request.Then in the View, I get the data (in componentDidMount()
)from store as a state and pass some properties of it to child components.
The data(this.state.store
), which is a Object, has two properties, called condition
and forecast
.The problem is that if I want to pass the this.state.store.condition
(or forecast
) to the child, it says TypeError: Cannot read property 'condition' of undefined
. But if I just try to access this.state.store
(for example, console.log(this.state.store)
), there is no error.
Also, if I try to access this.state.store.condition in a try-catch
statement, and log the error when there is one, I do access the condition successfully with the console printed TypeError
above mentioned.
Here is my codes:
store:
const CHANGE_EVENT = 'change';
let _app = {};
// create a city
function create(city, data) {
_app[city.toUpperCase()] = {
condition: data.condition,
forecast: data.forecast,
};
}
const AppStore = Object.assign({}, EventEmitter.prototype, {
getAll() {
return _app;
},
emitChange() {
this.emit(CHANGE_EVENT);
},
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
// register callback
AppDispatcher.register((action) => {
switch (action.actionType) {
case AppConstants.CREATE_CITY: {
create(action.city, action.data);
AppStore.emitChange();
break;
}
// other cases
default:
// noop
}
});
actions:
function callback(city, data) {
console.log(data);
const action = {
actionType: AppConstants.CREATE_CITY,
city,
data,
};
AppDispatcher.dispatch(action);
}
const AppActions = {
create(city) {
getDataFromAPI(city, callback);
},
};
utils:
function getDataFromAPI(query, callback) {
let data;
const url = `https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where u='c' AND woeid in (select woeid from geo.places(1) where text="${query}")&format=json`;
superagent
.get(url)
.use(jsonp)
.end((err, res) => {
console.log(res.body.query.results.channel.item);
data = res.body.query.results.channel.item;
callback(query, data);
});
}
views:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
store: Store.getAll(),
currentCity: 'BEIJING',
};
this.onChange = this.onChange.bind(this);
this.getCurrentCity = this.getCurrentCity.bind(this);
}
componentWillMount() {
AppActions.create('BEIJING');
}
componentDidMount() {
Store.addChangeListener(this.onChange);
}
onChange() {
this.setState({ store: Store.getAll() });
}
getCurrentCity(city) {
this.setState({ currentCity: city.toUpperCase() });
}
componentWillUnmout() {
Store.removeChangeListener(this.onChange);
}
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
// console.log(this.state.store[this.state.currentCity])<--UNDEFINED
// console.log(this.state.store[this.state.currentCity].condition);<--CANNOT READ PROPERTY
^
|
ERROR ON THIS 2 STATEMENTS
try {
condition = this.state.store[this.state.currentCity].condition;
forecast = this.state.store[this.state.currentCity].forecast;
text = condition.text.toUpperCase();
temp = condition.temp;
currentWeatherCode = condition.code;
currentWeatherClassName = setWeatherIcon(currentWeatherCode);
date = condition.date;
for (let i = 0; i < 6; i++) {
forecastWeatherCode.push(forecast[i].code);
forecastWeatherClassName.push(setWeatherIcon(forecastWeatherCode[i]));
forecastDate.push(forecast[i].date);
}
} catch (err) {
console.log(err);<--STILL ERROR, BUT I DO ACCESS THE PROP CONDITION IN THIS WAY
}
return (
<div>
<Today
city={this.state.currentCity}
weatherStatus={text}
tempreture={temp}
currentWeatherClassName={currentWeatherClassName}
date={date}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('#app'));
It seems to me that you are trying to access the this.state.store[this.state.currentCity]
property before it is fetched from the remote API.
You could add some sort of indication that the data is still being fetched like this.
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
if (!this.state.store.hasOwnProperty(this.state.currentCity)) {
return <div>Loading...</div>;
}
... the rest of your original code
}
When it is done loading the setState() method is invoked and render() is called again. The second time it will fall trough the if and run your code.