1

I am new to React/redux with Node. I am working on a full stack app that utilizes Node.js on the server side and React/Redux on the client side. One of the functions of the app is to provide a current and eight-day weather forecast for the local area. The Weather route is selected from a menu selection on the client side that menu selection corresponds to a server side route that performs an axios.get that reaches out and consumes the weather api (in this case Darksky) and passes back that portion of the JSON api object pertaining to the current weather conditions and the eight-day weather forecast. There is more to the API JSON object but the app consume the "current" and "daily" segment of the total JSON object.

I have written a stand-alone version of the server-side axios "get" that successfully reaches out to the Darksky API and returns the data I am seeking. I am, therefore, reasonably confident my code will correctly bring back the data that I need. My problem consists in this: when I try to render the data in my React Component, the forecast object is undefined. That, of course, means there is nothing to render.

I have reviewed my code, read a plethora of documentation and even walked through tutorials that should help me find the problem and it still eludes me. So, I am stuck and would greatly appreciate some help. Most of the comment you still in the code below will be removed after the debugging process is completed.

I am including code blocks relevant to the problem:

My React Component

// client/src/components/pages/functional/Weather.js

import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Moment from 'react-moment';

import Spinner from '../../helpers/Spinner'
import { getWeather } from '../../../redux/actions/weather'

const Weather = ({ getWeather, weather: { forecast, loading } }) => {

  // upon load - execute useEffect() only once -- loads forecast into state
  useEffect(() => { getWeather(); }, [getWeather])
  
  return (
    <div id='page-container'>
      <div id='content-wrap' className='Weather'>
        { loading ?
          <Spinner /> :
          <>
            <div className='WeatherHead box mt-3'>
              <h4 className='report-head'>Weather Report</h4>
            </div>
            {/* Current Weather Conditions */}
            <h6 className='current-head'>Current Conditions</h6>
            <section className='CurrentlyGrid box mt-3'>

              /* additional rendering code removed for brevity */

              <span><Moment parse='HH:mm'>`${forecast.currently.time}`</Moment></span>

              /* additional rendering code removed for brevity */

            </section>
          </>
        }
      </div>
    </div>
  );
};

Weather.propTypes = {
  getWeather: PropTypes.func.isRequired,
  weather: PropTypes.object.isRequired
};

const mapStateToProps = state => ({ forecast: state.forecast });

export default connect( mapStateToProps, { getWeather } )(Weather);

My React Action Creator

// client/src/redux/actions/weather.js

import axios from 'axios';
import chalk from 'chalk';

// local modules
import {
  GET_FORECAST,
  FORECAST_ERROR
} from './types';

// Action Creator
export const getWeather = () => async dispatch => {

  try {
    // get weather forecast
    const res = await axios.get(`/api/weather`);

    console.log(chalk.yellow('ACTION CREATOR getWeather ', res));

    // SUCCESS - set the action -- type = GET_WEATHER & payload = res.data (the forecast)
    dispatch({
      type: GET_FORECAST,
      payload: res.data
    });
  } catch (err) {
    // FAIL - set the action FORECAST_ERROR, no payload to pass
    console.log('FORECAST_ERROR ',err)
    dispatch({
      type: FORECAST_ERROR
    });
  };
};

My React Reducer

// client/src/redux/reducers/weather.js

import { 
  GET_FORECAST,
  FORECAST_ERROR,
 } from '../actions/types'

const initialState = {
  forecast: null,
  loading: true
}

export default (state = initialState, action) => {
  const { type, payload } = action

  switch (type) {
    case GET_FORECAST:
      return {
        ...state,
        forecast: payload,
        loading: false
      }
    case FORECAST_ERROR:
      return {
        ...state,
        forecast: null,
        loading: false
      }
    default:
      return state
  }
}

My Node Route

// server/routes/api/weather.js

const express = require('express');
const axios = require('axios');
const chalk = require('chalk');

const router = express.Router();


// *****  route: GET to /api/weather 
router.get('/weather', async (req, res) => {
  try {
    // build url to weather api
    const keys = require('../../../client/src/config/keys');
  
    const baseUrl = keys.darkskyBaseUrl;
    const apiKey = keys.darkskyApiKey;
    const lat = keys.locationLat;
    const lng = keys.locationLng;
    const url = `${baseUrl}${apiKey}/${lat},${lng}`;

    console.log(chalk.blue('SERVER SIDE ROUTE FORECAST URL ', url));

    const res = await axios.get(url);

    // forecast -- strip down res, only using currently{} & daily{}
    const weather = {
      currently: res.data.currently,
      daily: res.data.daily.data
    };

    console.log(chalk.yellow('SERVER SIDE ROUTE FORECAST DATA ', weather));

    // return weather
    res.json({ weather });

  } catch (error) {
    console.error(chalk.red('ERR ',error.message));
    res.status(500).send('Server Error');
  }
});

module.exports = router;

My Express server middleware pertaining to routes (just to be thorough)

// server/index.js

/* code deleted for brevity */

// define routes
app.use('/api/users', require('./routes/api/users'));
app.use('/api/auth', require('./routes/api/auth'));
app.use('/api/weather', require('./routes/api/weather'));
app.use('/api/favorites', require('./routes/api/favorites'));

/* code deleted for brevity */

If the code snippets included are not sufficient, the repo resides here: https://github.com/dhawkinson/TH12-BnBConcierge

Thank you in advance for help with this.

***** Updates *****

  1. I notice that the console logs I have in both actions/weather.js & reducers/weather.js on the client side & routes/api/weather.js on the server side are NOT firing. That tells me that those modules must not be executing. That would explain why I am getting the error "Cannot read property 'currently' of undefined" in client/src/components/pages/functional/Weather.js. Clearly I have a missing link in this chain. I just can't see what it is.

  2. I tried a small refactor, based on input below. I was trying to see if there was some kind of naming conflict going on. this is what I did in my React functional Component:

// client/src/components/pages/functional/Weather.js

...

const mapStateToProps = state => ({weather: { forecast: state.forecast, loading: state.loading }});

...

It didn't help.

2 Answers 2

1

I see that in your combineReducers here you are setting as

export default combineReducers({
  alert,
  auth,
  weather

})

So in the store, things gets saved as { alert: {...}, auth: {...}, weather: {...}}. Can you try accessing the forecast value in your Weather as state.weather.forecast ?

const mapStateToProps = state => ({ forecast: state.weather.forecast });

Let me know if it works.

Sign up to request clarification or add additional context in comments.

2 Comments

I'll try that and let you know.
I tried that and a modification of that. Neither worked. But thanks for the input.
0

You need to modify your component.

const dispatch = useDispatch();

useEffect(() => { dispatch(getWeather()); }, [getWeather])

And your mapToStateToProps should be as follows:

const mapStateToProps = state => ({ forecast: state.weather.forecast });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.