Upon page load, I am dispatching, inside my index.js
, an action store.dispatch(getWeatherReports());
that hits a weather API. This action goes through the redux
process and eventually adds the data returned to a property on the state called weatherReports
. This property is an object with an empty array. Now, I'm going to paste in an overview of the code.. not all of it to save you the trouble from going line to line, as outputting the data from the API is NOT the issue I am having.
Here is my index.js
:
import 'babel-polyfill';
import React from 'react';
import {render} from 'react-dom';
import configureStore from './store/configureStore';
import {Provider} from 'react-redux';
import {Router, browserHistory} from 'react-router';
import {StyleRoot} from 'radium';
import routes from './routes';
import {loadBlogs} from './actions/blogActions';
import {loadUsers, getActiveUser} from './actions/userActions';
import {getWeatherReports} from './actions/weatherActions';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import '../node_modules/toastr/build/toastr.min.css';
import './styles/app.scss';
const store = configureStore();
store.dispatch(loadBlogs());
store.dispatch(loadUsers());
store.dispatch(getActiveUser());
store.dispatch(getWeatherReports());
render(
<StyleRoot>
<Provider store={store}>
<Router history={browserHistory} routes={routes}/>
</Provider>
</StyleRoot>,
document.getElementById('app')
);
Trust that this goes through the right process to return the data. I have then created a smart
component that takes this state and looks to pass it to a dumb component.
class DashboardPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
weatherReports: []
};
}
<ContentBox
title="What The Wind Blew In"
content={<WeatherReport reports={this.props.weatherReports} />
/>
Again, trust that I have appropriately mapStateToProps
, mapDispatchToProps
, etc. I then look to display the data in the dumb
component. WeatherReport.js
:
import React, {PropTypes} from 'react';
import styles from '../common/contentBoxStyles';
const WeatherReport = ({reports}) => {
console.log(reports);
return (
<div style={styles.body} className="row">
<div style={styles.weatherBoxContainer}>
<div className="col-sm-2 col-md-offset-1" style={styles.weatherBoxContainer.weatherCard}>
<div style={styles.weatherBoxContainer.weatherReport}>
<div style={styles.weatherBoxContainer.currentTemp}>
HERE IS MY PROBLEM!!
{reports[0].main.temp}
</div>
</div>
CA
</div>
<div className="col-sm-2" style={styles.weatherBoxContainer.weatherCard}>
<div style={styles.weatherBoxContainer.weatherReport}>
Report
</div>
UT
</div>
<div className="col-sm-2" style={styles.weatherBoxContainer.weatherCard}>
<div style={styles.weatherBoxContainer.weatherReport}>
Report
</div>
MN
</div>
<div className="col-sm-2" style={styles.weatherBoxContainer.weatherCard}>
<div style={styles.weatherBoxContainer.weatherReport}>
Report
</div>
DC
</div>
<div className="col-sm-2" style={styles.weatherBoxContainer.weatherCard}>
<div style={styles.weatherBoxContainer.weatherReport}>
Report
</div>
NY
</div>
</div>
</div>
);
};
WeatherReport.propTypes = {
reports: PropTypes.array
};
export default WeatherReport;
My underlying issue is that before my dispatch action returns the data.. I am trying to render it through my dumb
component. Thus I receive the following error when trying to access the data: Uncaught TypeError: Cannot read property 'main' of undefined(…)
when doing the following: reports[0].main.temp
What is happening is this is stopping my application from moving forward so that the store.dispatch(getWeatherReports());
never gets called. Thus if I remove the {reports[0].main.temp}
from the equation, then the process continues and the action gets dispatched. Then I can call the equation w/ HMR and hooray!! The data is there...
So my question, and thanks for bearing with me, is how can I get it so that my dumb component waits to try and access these properties on the state until AFTER my initial dispatched actions occur?
If that getWeatherReports request is being made by something that supports promises rather than callbacks to notify you of a successful response (i.e. something like axios), then you could try and use a middleware like redux-promise.
What redux-promise will do is intercept the dispatch action and stops it from forwarding the action to the store's subscribers if the promise has not been resolved.
Once you get a successful response back and the promise is resolved, the middleware creates a new action with the same action type as the original but with a payload that contains the data you need.
You'd use it as follows:
Seems like
store.dispatch(getWeatherReports())
is an asynchronous API call. So you need to wait for the response before you render your component in the DOM.Solution: use redux-connect.
Key points:
Your component won't render until the promise is fulfilled.
The main advantages are:
Simple and elegant to configure for both CSR (Client-Side Rendering) and SSR (Server-Side Rendering).
you can react on fetching actions like data loading or load success in your own reducers
@connect
finally, you can debug and see your data using Redux Dev Tools
Also it's integrated with React Router to prevent routing transition until data is loaded.
Make sure you don't forget the 4 steps of configuration (described here):
Router
with ReduxAsyncConnect middlewareSo in
DashboardPage
(assuminggetWeatherReports()
will return a promise), what we can do is:if you have babel presets for using decorators:
otherwise:
Use multipe promises,
Check if
reports[0]
exists before trying to access.main
. If it'sundefined
then display a loading spinner or something...