React + Redux: Separating the presentation from the data - reactjs

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);

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.

I'm a react-redux noob myself :-) and I've come across similar issues.
As far as I can tell, the container/presentational separation you've made looks good, but you can go even a step further and separate the container's fetching and mounting.
The solution I'm referring to is what people variously call "higher-order components" and "wrapper components": (the code below isn't tested - it's just for illustration purposes)
import {connect} from blah;
const AppWrap = (Wrapped) => {
class AppWrapper extends Component {
constructor(props) {
super(props);
this.state = {foo: false};
}
componentWillMount() {
this.props.actions.fooAction()
.then(() => this.setState({foo: false}));
}
render() {
return (<Wrapped {...this.props} foo={this.state.foo}/>);
}
}
function mapState(state) { blah }
function mapDispatch(dispatch) { blah }
return connect(mapState, mapDispatch)(AppWrapper);
}
export default AppWrap;
Notice the = (Wrapped) => { part at the top. That is what's doing the actual "wrapping", and the argument can be named anything so long as you refer to it in the render hook.
Now inside your AppContainer, you get a this.props.foo which acts as a flag telling you that fooAction() has completed, and you can use it to render your presentational components accordingly. Until fooAction completes, you can be sure that the foo passed into AppContainer will be false.
To put what I just said into code, your AppContainer might look something like this:
import AppWrapper from './AppWrapper';
class AppContainer extends Component {
constructor(props) {
super(props);
}
render() {
return (!this.props.foo) ? <div>bar</div> : (
<div blah>
<SearchBar blah/>
<CurrentWeather blah/>
</div>
);
}
}
export default AppWrapper(AppContainer);
The benefit of using a wrapper component like this is that
you can take more control over how and when exactly the data gets rendered
account for "loading" mechanisms and logic
avoid quirky problems like having to make dispatches within componentWillMount hooks and having to deal with the consequences.
Take a look at this blog post for more information about HoCs: https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

Related

React.js - how to pass event handlers to deeply nested component without props drilling?

I have the structure of components (nested) that seems like this:
Container
ComponentA
ComponentB
ComponentC(want to handle event here with state that lives on container)
Do I need to pass as props all the way from Container, ComponentA, ComponentB and finally ComponentC to have this handler? Or is there another way like using Context API?
I'm finding a bit hard to handle events with react.js vs vue.js/angular.js because of this.
I would recommend using either Context API (as you mentioned) or Higher Order Components (HoC)
Context Api is your data center. You put all the data and click events that your application needs here and then with "Consumer" method you fetch them in any component regardless of how nested it is. Here is a basic example:
context.js //in your src folder.
import React, { Component, createContext } from "react";
import { storeProducts } from "./data"; //imported the data from data.js
const ProductContext = createContext(); //created context object
class ProductProvider extends Component {
state = {
products: storeProducts,
};
render() {
return (
<ProductContext.Provider
//we pass the data via value prop. anything here is accessible
value={{
...this.state,
addToCart: this.addToCart //I wont use this in the example because it would
be very long code, I wanna show you that, we pass data and event handlers here!
}}
>
// allows all the components access the data provided here
{this.props.children},
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Now we set up our data center with .Consumer and .Provider methods so we can access
here via "ProductConsumer" in our components. Let's say you want to display all your products in your home page.
ProductList.js
import React, { Component } from "react";
import Product from "./Product";
import { ProductConsumer } from "../context";
class ProductList extends Component {
render() {
return (
<React.Fragment>
<div className="container">
<div className="row">
<ProductConsumer>
//we fetch data here, pass the value as an argument of the function
{value => {
return value.products.map(product => {
return <Product key={product.id} />;
});
}}
</ProductConsumer>
</div>
</div>
</React.Fragment>
);
}
}
export default ProductList;
This is the logic behind the Context Api. It sounds scary but if you know the logic it is very simple. Instead of creating your data and events handlers inside of each component and prop drilling which is a big headache, just put data and your event handlers here and orchestrate them.
I hope it helps.

React + Redux: how to force Loading element to exist at least 0.5 seconds

Let's say it's common for my app to have such component:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loading from './Loading'
export default class Form extends Component {
componentWillMount () {
const { propogateValues, isFetching, fetchPath } = this.props
console.log(this.props)
if (isFetching) {
propogateValues(fetchPath)
}
}
render () {
const { handleSubmit, path, successPush, isFetching } = this.props
if (isFetching) {
return (<div> <Loading /></div>)
} else {
return (
<form onSubmit={(e) => { handleSubmit(e, path, successPush) }}>
{this.props.children}
</form>
)
}
}
}
Where Loading is pure presentational component representing loading animation.
Issue: when receive action is dispatched and component receives new isFetching: false prop, it rerenders component. However, from interface perspective, I would like Loading to exist at least some time (.5 second), because very fast switch makes it looks worse than without such component at all.
Not sure how to implement it, should I really use redux store here or there is better approach?
Adding react-delay component should help create the effect.
<div>
<Delay wait={500}>
<Loading />
</Delay>
</div>
The source code for Delay is quite simple to understand.

Which way should I use for Connector in Redux?

I seen 2 ways of doing the same thing but I am not sure what is the proper way.
Component
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index'
class UserList extends Component {
renderList() {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.first} {user.last}
</li>
);
});
}
render() {
return (
<ul>
{this.renderList()}
</ul>
);
}
}
// Get apps state and pass it as props to UserList
// > whenever state changes, the UserList will automatically re-render
function mapStateToProps(state) {
return {
users: state.users
};
}
// Get actions and pass them as props to to UserList
// > now UserList has this.props.selectUser
function matchDispatchToProps(dispatch){
return bindActionCreators({selectUser: selectUser}, dispatch);
}
// We don't want to return the plain UserList (component) anymore, we want to return the smart Container
// > UserList is now aware of state and actions
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
https://github.com/buckyroberts/React-Redux-Boilerplate
Or
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
fetchTweets() {
this.props.dispatch(fetchTweets())
}
render() {
const { user, tweets } = this.props;
if (!tweets.length) {
return <button onClick={this.fetchTweets.bind(this)}>load tweets</button>
}
const mappedTweets = tweets.map(tweet => <li>{tweet.text}</li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
https://github.com/learncodeacademy/react-js-tutorials/tree/master/5-redux-react
The first way uses 2 different functions mapStateToProps() and matchDispatchToProps() while the other way uses #connect(....).
When I use the #connect I get a whole bunch of warnings saying that it has not been finalized and might change.
The # symbol is a decorator which is still considered experimental. So I would use that at your own risk. Your first code block is the safer way to do it as described in the official docs. Both blocks essentially do the same thing but decorators are more sugar than anything.
References:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
What's the '#' (at symbol) in the Redux #connect decorator?
I think the first method will give you less problems in the end. Someone else can chime in though too.
The answer by Jackson is right in every sense however he is missing out the importance of using the first version for the usage of unit testing. If you want to be able to unit test a component (which usually means testing with the unconnected version) you need to be able to export the connected and unconnected component.
Using your example and assuming you are using jest/enzyme you could do something like this:
// notice importing the disconnected component
import { UserList } from '../relative/file/path/UserList'
import { mount } from 'enzyme'
describe('UserList', () => {
it('displays the Username', () => {
const users = [{fist: 'Person', last: 'Thing'}, ... ]
const UserList = mount(<UserList users={users} />)
export(UserList.find('li')[0].text()).toEqual('Person Thing')
});
});
Once you build larger projects being able to unit test will provide sanity to your coding life. Hope this helps

Initialize component with Async data

I'm trying to figure out how and where to load the data (ie call dispatch on my action) for my select box in react + redux + thunk. I'm not sure if it should go in the constructor of my App container, or should i load it inside my component (in my example: "MyDropdown")
My main App:
import MyDropdown from '../components/mydropdown';
// Should i import my action here and then...
// import { loadData } from '../actions';
class App extends Component {
render() {
return (
<div className="page-content">
<div className="option-bar">
// SEND it as a PROP inside MyDropdown...
<MyDropdown />
</div>
</div>
);
}
}
export default App;
My Component
// OR.. Should i load it in my MyDropdown component here?
import { loadData } from '../actions';
class MyDropdown extends Component {
// If i load it here on load, how do i do it?
render() {
return(
<select>
{renderOptions()}
</select>
);
}
}
I've tried componentDidMount() inside my App class, but it didnt seem to work. It seems to make sense to put the initialize data and call to actions there as it'll be all centralized, instead of calling actions inside my child components. Also, i'll have multiple select boxes that need to be loaded on startup, so my App class might grow quite a bit, is that the correct way to do it? I'm not sure what the best practice is as i've only just started learning react.
You should separate data components from presentation components (see post here).
So in your small example, MyDropdown should be passed all the data it needs in order to render the component. That would mean fetching the data in App (or some parent component of the component actually rendering the view.
Since you're working with React and Redux, the react-redux library provides a helper function to generate containers that fetch the data required for your presentation component.
To do that, change App to:
import { connect } from 'react-redux'
import MyDropdown from '../components/mydropdown';
import { loadData } from '../actions';
// This class is not exported
class App extends Component {
componentDidMount() {
this.props.loadData()
}
render() {
return (
<div className="page-content">
<div className="option-bar">
<MyDropdown data={this.props.data}/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { data } = state
return {
data
}
}
function mapDispatchToProps(dispatch) {
return {
loadData(){
dispatch(loadData())
}
}
}
// Export a container that wraps App
export default connect(mapStateToProps, mapDispatchToProps)(App);
Alternatively, you could keep App the same and change MyDropdown to:
import { connect } from 'react-redux'
import { loadData } from '../actions';
// Exporting this allows using only the presentational component
export class MyDropdown extends Component {
componentDidMount() {
this.props.loadData()
}
render() {
return(
<select>
{renderOptions(this.props.data)}
</select>
);
}
}
function mapStateToProps(state) {
const { data } = state
return {
data
}
}
function mapDispatchToProps(dispatch) {
return {
loadData(){
dispatch(loadData())
}
}
}
// By default, export the container that wraps the presentational component
export default connect(mapStateToProps, mapDispatchToProps)(MyDropdown);
In both cases, look at what is actually being exported as default at the end. It's not the component; it's the return of connect. That function wraps your presentational component and returns a container that is responsible for fetching the data and calling actions for the presentational component.
This gives you the separation you need and allows you to be flexible in how you use the presentation component. In either example, if you already have the data you need to render MyDropdown, you can just use the presentation component and skip the data fetch!
You can see a full example of this in the Redux docs here.

React redux generated stateless components perfomance

I am learning redux and react. And i decided to run a simple "stress test" with lets say 15k rows of generated component (i hope i did it right).
So i have stateless component which receive common prop for example 'year'. And i want to clone this stateless component over 9000 times and update them. For example change it prop(year) from 2016 to 2015.
I built this component in my testing project and it's working, but with slow response especially in IE 11. I am new to react+redux and maybe i did something wrong in my code.
As suggested in discord chat room i have added into my Page component:
shouldComponentUpdate(nProps, nState) {
return nProps.year != this.props.year;
}
This did help a bit. But it is still slow.
Also as related question - Is it ok to use lodash.assign() to update my state?
Also i am using typescript and it seems to have not built-in polyfill for Object.assign(); That's why i decided to try lodash.
So here is my top base component app.tsx:
import * as React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as pageActions from '../actions/page';
import User from '../components/user/User';
import Page from '../components/page/Page';
class App extends React.Component<any, any> {
render() {
const { user, page } = this.props;
const { setYear } = this.props.pageActions;
return (
<div>
<User name={user.name} />
<Page photos={page.photos} year={page.year} setYear={setYear} />
</div>
);
};
}
function mapStateToProps (state) {
return {
user: state.user, // (1)
page: state.page // (2)
};
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
And this is my page reducer:
import {assign} from 'lodash';
const INITIAL_STATE = {
year: 2016,
photos: []
};
function pageReducer(state = INITIAL_STATE,
action = {type: '', payload: null}) {
switch (action.type) {
case 'SET_YEAR':
return assign({}, state, {year: action.payload});
default:
return state;
}
}
export default pageReducer;
And Page component:
import * as React from 'react';
import {range} from 'lodash';
let StatelessSpan: React.StatelessComponent<any> = (props) => (
<span>{props.year} </span>
);
class Page extends React.Component<any, any> {
constructor(props) {
super(props);
}
private onYearBtnClick = (e) => {
this.props.setYear(+e.target.innerText);
};
shouldComponentUpdate(nProps, nState) {
return nProps.year != this.props.year;
}
render() {
const {year, photos} = this.props;
let years = range(15000).map((value, index) => {
if(index % 4===0){
return <StatelessSpan key={index} year={year} />;
}
return <span key={index}>i am empty</span>
});
return <div>
<p>
<button onClick={this.onYearBtnClick}>2016</button>
<button onClick={this.onYearBtnClick}>2015</button>
<button onClick={this.onYearBtnClick}>2014</button>
</p>
{years}
</div>;
};
}
export default Page;
One told me that innerText is experimental and non-stable, so i've changed it to textContent. Still got delay in IE.
React/Redux may be THE best way to write apps, but it's important to understand that elegancy can sometimes come at the cost of performance issues. Luckily, it's much easier to take an elegant solution and make it performant than the other way around.
I could throw a bunch of performance optimization tips at you for React and Redux, but you might be optimizing the wrong things. You need profile your app and find out performance issues you are running into.
You might find this talk extremely helpful: https://www.youtube.com/watch?v=5sETJs2_jwo. Netflix has been able to start with very slow React and really make things go super fast without making a mess of things.
I've found this discussion here: https://twitter.com/mweststrate/status/720177443521343488
So this partially answers my question about performance and gives good vision on how this both libraries behave with my case.

Resources