Cody Cody - 1 month ago 9
React JSX Question

React + Redux: Separating the presentation from the data

I am building a weather app with React & Redux. I've decided to venture into uncharted waters as a noob to React & Redux. I'm splitting things up into presentational components and their respective container that will handle the data. I'm having some problems wrapping my head around this though. It might come down to how I'm trying to do it I'm just really unsure.

Right now I have

SearchBar
,
CurrentWeather
, &
Forecast
components and an
AppContainer
that I'm trying to integrate those components into. I have the
SearchBar
component integrated into the
AppContainer
so far and it is working with no problems. Here is where I am getting confused. So I have provided the needed actions and components to the container and the container has been
connected
so when the user does a search the api call will be made and the state will update through the reducers.

That data should be available through
mapStateToProps
now correct?

How can I go about using that data after the user has performed the action but have it not be used upon the initial render? If
AppContainer
is rendering these three components I will obviously be passing props to them so they render and function as they are expected to. I'm thinking this is where a lifecycle could be used I'm just unsure of which or how to use them. My code for the
AppContainer
,
SearcBar
, &
CurrentWeather
are below.
CurrentWeather
&
Forecast
are nearly identical (only providing different data from different endpoints for the api) so I did not provide it. I also didn't provide the actions or reducers because I know they work fine before I decided to attempt this refactor. Maybe I need more than one container to pull this off? Any advice or direction would be greatly appreciated, thanks all and have a good night.

** Do have a side question: on
_weatherSearch
I have
event.preventDefault();
because the
SearchBar
is a form element. Do I even need to provide this? If
event
is not what is being passed but the
term
I think no. The
event
is being used as seen below in the form element of
SearchBar
:

onSubmit={event => getWeather(event.target.value)}


App Container:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCurrentWeather, fetchForecast } from '../actions/actions';

import SearchBar from '../components/SearchBar';
import CurrentWeather from '../components/CurrentWeather';

class AppContainer extends Component {

_weatherSearch(term) {
event.preventDefault();
// Here is where we go to fetch weather data.
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}

render() {
const getWeather = term => {this._weatherSearch(term);};
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
<CurrentWeather />
</div>
);
}
}

const mapStateToProps = ({ current, forecast }) => {
return {
current,
forecast
}
}

export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);


SearchBar:

import React from 'react';

const SearchBar = ({ getWeather }) => {
return(
<form className='input-group' onSubmit={event => getWeather(event.target.value)}>
<input
className='form-control'
placeholder='Search a US City' />
<span className='input-group-btn'>
<button className='btn btn-secondary' type='submit'>Submit</button>
</span>
</form>
);
}

export default SearchBar;


CurrentWeather: *NOTE: I have not removed any of the logic or data processing from
CurrentWeather
yet so it has not been refactored to a presentational only component yet.

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {unitConverter} from '../conversions/conversions_2.0';

export class CurrentWeather extends Component {
_renderCurrentWeather(cityData) {
const name = cityData.name;
const {temp, pressure, humidity} = cityData.main;
const {speed, deg} = cityData.wind;
const {sunrise, sunset} = cityData.sys;
return (
<tr key={name}>
<td>{unitConverter.toFarenheit(temp)} F</td>
<td>{unitConverter.toInchesHG(pressure)}"</td>
<td>{humidity}%</td>
<td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td>
</tr>
);
}

render() {
let currentWeatherData = [];
if (this.props.current) {
currentWeatherData = this.props.current.map(this._renderCurrentWeather);
}
return (
<table className="table table-reflow">
<thead>
<tr>
<th>Temperature</th>
<th>Pressure</th>
<th>Humidity</th>
<th>Wind</th>
</tr>
</thead>
<tbody>
{currentWeatherData}
</tbody>
</table>
);
}
}

function mapStateToProps({current}) {
return {current};
}

export default connect(mapStateToProps)(CurrentWeather);

DDS DDS
Answer

Your render function is very dynamic. You can omit anything you like:

class AppContainer extends Component {

  _weatherSearch(term) {
    // event.preventDefault(); We can't do this because we don't have an event here...

    this.props.fetchCurrentWeather(term);
    this.props.fetchForecast(term);
    }

  render() {
    const getWeather = term => { this._weatherSearch(term); };
    return (
      <div className="application">
        <SearchBar getWeather={getWeather}/>
        { Boolean(this.props.current) && <CurrentWeather /> }
      </div>
    );
  }
}

const mapStateToProps = ({ current }) => ({ current });

export default connect(mapStateToProps,
  { fetchCurrentWeather, fetchForecast })(AppContainer);

This is how you deal with missing data. You just either show nothing, or a message to search first, or if it's loading,you can show a spinner or throbber.

The technique used above to hide CurrentWeather is to pass a Boolean to React if we're wanting to hide the component. React ignores true, false, null and undefined.

Note that it's a good idea to only ever pass data in mapStateToProps that you'll actually be using inside the component itself. In your code you're passing current and forecast but you don't use them.

Redux will rerender when any of the mapStateToProps, mapDispatchToProps or props data changes. By returning data you'll never use you instruct Redux to rerender when it's not necessary.