Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Having trouble on FSJS Capstone project, moving data between front end and back end.

This is for my FSJS Developer Capstone project. 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

import React, { useEffect } from 'react';
import { connect, useDispatch } 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, forecast, loading }) => {

  const dispatch = useDispatch();

  // upon load - execute useEffect() only once -- loads forecast into state
  useEffect(() => { dispatch(getWeather()); }, [dispatch, 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'>
              <span>Now</span>
              <span>Summary</span>
              <span>Icon</span>
              <span>Precip</span>
              <span>Temp</span>
              <span>Humid</span>
              <span>Visblty</span>
              <span>Wind Spd</span>
              <span><Moment parse='HH:mm'>`${forecast.currently.time}`</Moment></span>
              <span>`${forecast.currently.summary}`</span>
              <span>`${forecast.currently.icon}`</span>
              <span>`${forecast.currently.precipProbability}`</span>
              <span>`${forecast.currently.temperature}`</span>
              <span>`${forecast.currently.humidity}`</span>
              <span>`${forecast.currently.visibility}`</span>
              <span>`${forecast.currently.windSpeed}`</span>
            </section>
            {/* Eight Day Forecast */}
            {/* Code omitted for brevity */}
};

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

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

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

My React Action Creator

// node modules
import axios from 'axios';
import chalk from 'chalk';

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

// Action Creator

export const getWeather = () => async dispatch => {

  console.log(chalk.blue('ACTION CREATOR got here '));

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

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

    // pass only the currently & daily segments of the api
    const forecast = {
      currently: res.data.currently,
      daily: res.data.daily.data
    }

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

My React Reducer

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

// node modules
const express = require('express');
const axios = require('axios');
const chalk = require('chalk');

const router = express.Router();

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;

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

Thank you in advance for help.

Blake Larson
Blake Larson
13,014 Points

If you are console logging the forecast after the get request...

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

    const forecast = {
      currently: res.data.currently,
      daily: res.data.daily.data
    }

   console.log(forecast); // <---------------------------------

    dispatch({
      type: GET_FORECAST,
      payload: forecast
    });

and get the proper result you can probably just use conditional rendering for the forecast like you did with loading.

{ loading || !forecast ? 'spinner' : 'content' }

There is also an empty opening tag on Weather.js line 27, but I think that would throw a syntax error and has nothing to do with the state being empty.

1 Answer

@Blake Larson -- Two things:

  1. I had the console.log to which you are referring in the position you suggest. It was not firing there either. I moved it to where it is now to try and grab 'res' before I set 'forecast'. It was a "fit of desperation" move that was no help.

  2. The empty opening tag <> is closed by an empty closing tag below </>. It is a React shortcut for for inserting a <Fragment></Fragment> (check it out).

I have a stand-alone routine that I wrote as a proof of concept. It builds the url and performs the axios.get(url) and brilliantly console.logs the correct resulting object. So, I know I have formulated the approach correctly.

My problem seems to be that my routing is not working AT ALL. I have strategic console.log()s placed throughout the code. NONE of them are firing so that tells me the routines are not being traversed. That's where I am stuck. There is some crazy little thing that is preventing me from getting the JSON back and therefore the render is returning -- 'Cannot read property "currently" of null.' It's driving me nuts.

I appreciate that you took a look at it though.