I've come across a repeated pattern in my react-redux site:
A component displays data from a web api, and it should be populated on load, automatically, without any user interaction.
I want to initiate the async fetch from a container component, but as far as I can tell the only way to do it is from a lifecycle event in a display component. This seems to make it impossible to put all the logic in the container and only use dumb stateless functional components for display.
This means I can't use a stateless functional component for any component that needs async data. That doesn't seem right.
It seems like the "right" way to do this would be to somehow initiate async calls from the container. Then when the call returned, the state would be updated and the container would get the new state and would in turn pass those to its stateless component via mapStateToProps().
Making async calls in mapStateToProps and mapDispatchToProps (I mean actually calling the async function, as opposed to returning it as a property) doesn't make sense.
So what I've ended up doing is putting the async call(s) in a refreshData() function exposed by mapDispatchToProps(), then calling it from two or more of the React lifecycle methods: componentDidMount and componentWillReceiveProps.
Is there a clean way to update the redux store state without putting lifecycle method calls in every component that needs async data?
Should I be making these calls higher up the component heierarchy (thereby reducing the scope of this issue, since only "top-level" components would need to listen to lifecycle events)?
Edit:
Just so there's no confusion what I mean by a connect()ed container component, here's a very simple example:
import React from 'react';
import { connect } from 'react-redux';
import {action} from './actions.js';
import MyDumbComponent from './myDumbComponent.jsx';
function mapStateToProps(state)
{
return { something: state.xxxreducer.something };
}
function mapDispatchToProps(dispatch)
{
return {
doAction: ()=>{dispatch(action())}
};
}
const MyDumbComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(MyDumbComponent);
// Uh... how can I hook into to componentDidMount()? This isn't
// a normal React class.
export default MyDumbComponentContainer;
Jamie Dixon has written a package to do this!
https://github.com/JamieDixon/react-lifecycle-component
Usage would look like this:
const mapDispatchToProps = {
componentDidMount: getAllTehDatas
}
...
export default connectWithLifecycle(mapStateToProps, mapDispatchToProps)(WrappedComponent)
edit
With hooks you are now able to implement lifecycle callbacks in a stateless functional component. While this may not directly address all of the points in the question, it may also get around some of the reason for wanting to do what was originally proposed.
edit to original answer
After the discussion in comments and thinking about it more, this answer is more exploratory and can serve as a piece of the conversation. But I don't think it's the right answer.
original answer
On the Redux site there's an example that shows you don't have to do both mapStateToProps and mapDispatchToProps. You can just leverage connect's awesomeness for the props, and use a class and implement the lifecycle methods on the dumb component.
In the example, the connect call is even in the same file and the dumb component isn't even exported, so to the user of the component it's looks the same.
I can understand not wanting to issue async calls from the display component. I think there's a distinction between issuing the async calls from there and dispatching an action which, with thunks, moves the issuing of the async calls up into the actions (even more decoupled from the React code).
As an example, here's a splash screen component where I'd like to do some async action (like asset preloading) when the display component mounts:
SplashContainer.js
import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'
const mapStateToProps = (state) => {
return {
// whatever you need here
}
}
const mapDispatchToProps = (dispatch) => {
return {
onMount: () => dispatch(actions.splashMount())
}
}
const SceneSplash = connect(
mapStateToProps,
mapDispatchToProps
)(Splash)
export default SceneSplash
Splash.js
import React from 'react'
class Splash extends React.Component {
render() {
return (
<div className="scene splash">
<span className="fa fa-gear fa-spin"></span>
</div>
)
}
componentDidMount() {
const { onMount } = this.props
onMount()
}
}
export default Splash
You can see the the dispatch happens in the connected container, and you can imagine in the actions.splashMount() call we issue an async http request or do other async things via thunks or promises.
edit to clarify
Allow me to try to defend the approach. I re-read the question and am not 100% sure I'm addressing the main thing it's after, but bear with me. If I am still not quite on track, I have a modified approach below that may be closer to the mark.
"it should be populated on load" - the example above accomplishes this
"I want to initiate the async fetch from a container" - in the example it's not initiated from the display component or the container, but from an async action
"This seems to make it impossible to put all the logic in the container" - I think you can still put any additional logic needed in the container. As noted, the data loading code isn't in the display component (or the container) but in the async action creator.
"This means I can't use a stateless functional component for any component that needs async data." - in the above example the display component is stateless and functional. The only link is the lifecycle method invoking a callback. It need not know or care what that callback does. It is not a case of the display component trying to be the owner of async data fetching - it's merely letting the code that does handle that know when a particular thing has happened.
So far I'm attempting to justify how the example given meets the question's requirements. That said, if what you're after is having a display component that contains absolutely no code related to the async data load even by indirect callbacks - that is, the only link it has is to consume that data via the props it's handed when that remote data comes down, then I would suggest something like this:
SplashContainer.js
import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'
const mapStateToProps = (state) => {
return {
// whatever you need here
}
}
const mapDispatchToProps = (dispatch) => {
dispatch(actions.splashMount())
return {
// whatever else here may be needed
}
}
const SceneSplash = connect(
mapStateToProps,
mapDispatchToProps
)(Splash)
export default SceneSplash
Splash.js
import React from 'react'
class Splash extends React.Component {
// incorporate any this.props references here as desired
render() {
return (
<div className="scene splash">
<span className="fa fa-gear fa-spin"></span>
</div>
)
}
}
export default Splash
By dispatching the action in mapDispatchToProps you are letting the code for that action reside entirely in the container. In fact, you are starting the async call as soon as the container is instantiated, rather than waiting for the connected display component to spin up and be mounted. However, if you cannot begin the async call until the componentDidMount() for the display component fires, I think you're inherently bound to have code like in my first example.
I haven't actually tested this second approach to see if react or redux will complain about it, but it should work. You have access to the dispatch method and should be able to call it without problems.
To be honest, this second example, while removing all code related to the async action from the display component does kind of strike me as a bit funny since we're doing non-mapping-of-dispatch-to-props things in the eponymous function. And containers don't actually have a componentDidMount to run it in otherwise. So I'm a bit squirmy with it and would lean toward the first approach. It's not clean in the "feels right" sense, but it is in the "simple 1-liner" sense.
Check out redux-saga https://github.com/yelouafi/redux-saga. It's a redux middleware component that creates long-lived watchers that look for specific store actions and can trigger functions or generator functions in response. The generator syntax is particular nice for handling async, and redux-saga has some nice helpers that allow you to treat async code in a synchronous fashion. See some of their examples. https://github.com/yelouafi/redux-saga/blob/master/examples/async/src/sagas/index.js . The generator syntax can be hard to grok at first, but based on our experience this syntax supports extremely complex async logic, including debounce, cancellation and joining/racing multiple requests.
You can do it from a container. Just make a component that extends React.Component but name it with "Container" somewhere in the name. Then use that container's componentDidMount instead of using componentDidMount in the presentational (dumb) component that the container component renders. Reducer will see that you've dispatched an action still, and still update state so your dumb component will be able to get at that data..
I TDD but even if I didn't TDD I separate out my dumb vs container components via file. I hate having too much in one file, especially if mixing dumb vs. container stuff in the same file, that's a mess. I know people do it, but I think that's awful.
I do this:
src/components/someDomainFolder/someComponent.js (dumb component)
src/components/someDomainFolder/someComponentContainer.js (for example you might use React-Redux..have connected container not a connected presentational component..and so in someComponentContainer.js you DO have a react class in this file, as stated, just call it someComponentContainer extends React.Component for example.
Your mapStateToProps() and mapDispatchToProps() would be global functions of that connected container component outside that container class. And connect() would render the container, which would render the presentational component but this allows you to keep all your behavior in your container file, away from dumb presentational component code.
that way you have tests around someComponent that are structural/state based and you have behavior tests around the Container component. Much better route to be going with maintaining and writing tests, and maintaining and making things easy for yourself or other devs to see what's going on and to manage dumb vs. behavioral components.
Doing things this way, your presentational stuff is separated both physically by file AND by code convention. AND your tests are grouped around the right areas of code...not an intermingled mess. AND if you do this, and use a reducer that listens to update state, your presentational component can remain totally stupid....and just look for that updates state via props...since you're using mapStateToProps().
Following up on #PositiveGuy suggestion, here is example code of how to implement a container component that can utilize lifecycle methods. I think this is a pretty clean approach that maintains separation of concerns keeping the presentation component "dumb":
import React from 'react';
import { connect } from 'react-redux'
import { myAction } from './actions/my_action_creator'
import MyPresentationComponent from './my_presentation_component'
const mapStateToProps = state => {
return {
myStateSlice: state.myStateSlice
}
}
const mapDispatchToProps = dispatch => {
return {
myAction: () => {
dispatch(myAction())
}
}
}
class Container extends React.Component {
componentDidMount() {
//You have lifecycle access now!!
}
render() {
return(
<MyPresentationComponent
myStateSlice={this.props.myStateSlice}
myAction={this.props.myAction}
/>
)
}
}
const ContainerComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Container)
export default ContainerComponent
You CAN initiate an async fetch from the parent container (the smart container). You write the function in the smart container and you pass the function as a prop for the dumb container. For example:
var Parent = React.createClass({
onClick: function(){
dispatch(myAsyncAction());
},
render: function() {
return <childComp onClick={this.onClick} />;
}
});
var childComp = React.createClass({
propTypes:{
onClick: React.PropTypes.func
},
render: function() {
return <Button onClick={this.props.onClick}>Click me</Button>;
}
});
childComp is stateless since the onClick definition is determined by the parent.
EDIT: Added connected container example below, excluded other stuff for brevity. Doesn't really show much, and it's a bit cumbersome to setup on fiddle and stuff, point is, I do use lifecycle methods in connected containers and it works out fine for me.
class cntWorkloadChart extends Component {
...
componentWillReceiveProps(nextProps){
if(nextProps.myStuff.isData){
if (nextProps.myStuff.isResized) {
this.onResizeEnd();
}
let temp = this.updatePrintingData(nextProps)
this.selectedFilterData = temp.selectedFilterData;
this.selectedProjects = temp.selectedProjects;
let data = nextProps.workloadData.toArray();
let spread = [];
if(nextProps.myStuff.isSpread) {
spread = this.updateSelectedProjectSpread(nextProps);
for (var i = 0; i < data.length; i++) {
data[i].sumBillableHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumBillableHrsSelectedProjects.toFixed(1)) : 0;
data[i].sumCurrentBudgetHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumCurrentBudgetHrsSelectedProjects.toFixed(1)) : 0;
data[i].sumHistoricBudgetHrsSelectedProjects = spread[data[i].weekCode] ? Number(spread[data[i].weekCode].sumHistoricBudgetHrsSelectedProjects.toFixed(1)) : 0;
}
}
if (nextProps.potentialProjectSpread.length || this.props.potentialProjectSpread.length) { //nextProps.myStuff.isPpSpread) { ???? - that was undefined
let potential = nextProps.potentialProjectSpread;
let ppdd = _.indexBy(potential, 'weekCode');
for (var i = 0; i < data.length; i++) {
data[i].sumSelectedPotentialProjects = ppdd[data[i].weekCode] ? ppdd[data[i].weekCode].sumSelectedPotentialProjects.toFixed(1) : 0;
}
}
for (var i = 0; i < data.length; i++) {
let currObj = data[i];
currObj.sumCurrentBudgetHrs = currObj.currentBudgeted.sumWeekHours;
currObj.sumHistoricBudgetHrs = currObj.historicBudgeted.sumWeekHours;
currObj.fillAlpha = .6; //Default to .6 before any selections are made
//RMW-TODO: Perhaps we should update ALL line colors this way? This would clean up zero total bars in all places
this.updateLineColor(currObj, "sumSelectedPotentialProjects", "potentialLineColor", potentialLineColor);
this.updateLineColor(currObj, "sumHistoricBudgetHrs", "histLineColor", histBudgetLineColor);
this.updateLineColor(currObj, "sumHistoricBudgetHrsSelectedProjects", "histSelectedLineColor", selectedHistBudgetFillColor);
}
if(nextProps.myStuff.isSelectedWeek){
let currWeekIndex = nextProps.weekIndex.index;
let selectedWeek = data[currWeekIndex].fillAlpha = 1.0;
}
if(data.length > 0){
if(data[0].targetLinePercentages && data.length > 9) { //there are target lines and more than 10 items in the dataset
let tlHigh = data[0].targetLinePercentages.targetLineHigh;
let tlLow = data[0].targetLinePercentages.targetLineLow;
if (tlHigh > 0 && tlLow > 0) {
this.addTargetLineGraph = true;
this.upperTarget = tlHigh;
this.lowerTarget = tlLow;
}
}
else {
this.addTargetLineGraph = false;
this.upperTarget = null;
this.lowerTarget = null;
}
}
this.data = this.transformStoreData(data);
this.containsHistorical = nextProps.workloadData.some(currObj=> currObj.historicBudgeted.projectDetails.length);
}
}
...
render() {
return (
<div id="chartContainer" className="container">
<WorkloadChart workloadData={this.props.workloadData}
onClick={this.onClick}
onResizeEnd={this.onResizeEnd}
weekIndex={this.props.weekIndex}
getChartReference={this.getChartReference}
//projectSpread={this.props.projectSpread}
selectedRows={this.props.selectedRows}
potentialProjectSpread={this.props.potentialProjectSpread}
selectedCompany={this.props.selectedCompany}
cascadeFilters={this.props.cascadeFilters}
selectedRows={this.props.selectedRows}
resized={this.props.resized}
selectedFilterData={this.selectedFilterData}
selectedProjects={this.selectedProjects}
data={this.data}
upperTarget={this.upperTarget}
lowerTarget={this.lowerTarget}
containsHistorical={this.containsHistorical}
addTargetLineGraph={this.addTargetLineGraph}
/>
</div>
);
}
};
function mapStateToProps(state){
let myValues = getChartValues(state);
return {
myStuff: myValues,
workloadData: state.chartData || new Immutable.List(),
weekIndex: state.weekIndex || null,
//projectSpread: state.projectSpread || {},
selectedRows: state.selectedRows || [],
potentialProjectSpread: state.potentialProjectSpread || [],
selectedCompany: state.companyFilter.selectedItems || null,
brokenOutByCompany: state.workloadGrid.brokenOutByCompany || false,
gridSortName: state.projectGridSort.name,
gridSortOrder: state.projectGridSort.order,
cascadeFilters: state.cascadeFilters || null,
selectedRows: state.selectedRows || [],
resized: state.chartResized || false,
selectedPotentialProjects: state.selectedPotentialProjects || []
};
}
module.exports = connect(mapStateToProps)(cntWorkloadChart);
Related
I'm trying to learn and test React.PureComponent and it keeps rendering even though no state changes for that pure component.
My PureComponent is very simple and it accepts only one Redux Action function via connect hoc
import React from 'react';
import {
Container,
Button
} from 'reactstrap'
import { connect } from 'react-redux'
import { resetWorkouts } from '../actions/workoutApiActions'
class About extends React.PureComponent {
render () {
const { resetWorkouts } = this.props;
console.log('in about render...')
return (
<React.Fragment>
<Container>
<h2>Api Data Reset</h2>
<Button color="danger" onClick={resetWorkouts}>Reset Data</Button>
</Container>
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
export default connect(null, mapDispatchToProps)(About);
In the above code, you can see that, there is no state in the component. It only accepts the action function as props from connect. However, whenever I clicks on the Reset Data button, it keeps calling the render method as shown in the screenshot.
In the screenshot, I can see that, global state store has been changed whenever, I click a button. But, that state is not used in my PureComponent and it should be out of the scope and my component should ignore to re-render.
Or Redux Action functions are created every time the global state store has been changed. And passed as a new object to my PureComponent ?
Theoretically, I don't need to write my own shouldComponentUpdate function, right? I'm confused and could you please help me to understand about this behaviour?
My goal is I don't want my PureComponent to render again when a user clicks a button.
Updates:
I have tried like the following according to this article and it's still re-rendering
const mapDispatchToProps = {
resetWorkouts
};
this because react do a shallow comparison between the prevProps and the nextProps,
and you can control that only in the shouldComponentUpdate, react doesn't know that the dispatcher is the same one from the previous render, because you are using return inside the mapDispatchToProps function.
In your component and in your case, while the function will remain the same, you can go with two paths:
path 1:
override the shouldComponentUpdate life cycle hook, to be as the following:
shouldComponentUpdate(){
return false;
}
path 2:
get rid of the return inside mapDispatchToProps and simplify the connect so it be as the following:
`conncect(state => ({}), {
resetWorkouts: resetWorkouts})(YourComponent);`
using one of the above paths should make you good to go
The reason why your component is rendering is because everytime the following function executes:
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
your components receives a new instance of a property named resetWorkouts(because you're creating an inline array function). You may look at the ownProps to check if your component already have the resetWorkouts:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
resetWorkouts: ownProps.resetWorkouts || () => dispatch(resetWorkouts())
}
}
I'm still new to react/redux, after getting something like this to function
User.js
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
return (
<Profile />
)
}
export default connect(null, {fetchUser})(User);
Profile.js
class Profile extends React.Component {
render() {
const { user } = this.props
return (
<h1>{user.profile.name}</h1>
)
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {})(Profile)
actions.js
export const fetchUser = (.....) => dispatch => {
fetch()
.....
}
reducers.js
case FETCH_USER:
return {
...state,
user: action.payload.user
};
As I understand it, the User component calls an action (fetchUser) from connect on componentWillMount(). That action calls an api, gets the data and the reducer adds that to the store within the state. The Profile component can then use connect to map the data from fetchUser in the store and display that data.
After reading some tutorials including https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md
It looks like things can be simplified a bit without using classes.
If I were to change the User and Profile components to a more functional way, how would I do it?
eg.
const User = () => {
return (
<Profile />
)
}
how do I dispatch the fetchUser action and how do I simulate it to be called with the flow of componentWillMount()?
or am I just over complicating things?
There is also a way to support lifecycle methods in functional components.
https://www.npmjs.com/package/react-pure-lifecycle
import React from 'react';
import lifecycle from 'react-pure-lifecycle';
// create your lifecycle methods
const componentDidMount = (props) => {
console.log('I mounted! Here are my props: ', props);
};
// make them properties on a standard object
const methods = {
componentDidMount
};
const FunctionalComponent = ({children}) => {
return (
<div>
{children}
</div>
);
};
// decorate the component
export default lifecycle(methods)(FunctionalComponent);
I think you should keep using statefull components with redux...
https://medium.com/#antonkorzunov/2-things-about-purecomponent-you-probable-should-know-b04844a90d4
Redux connect — is a PureComponent.
Yes — a very important thing, a HoC for a molecule is a pure one. And works even inside other pure components. And gets store from a current context.
Same is working, for example, for styled-component — you can wrap it with PureComponent, but it will still react to Theme changes.
Solution is simple — bypass logic, use old school events bus, subcribe, wait and emit events.
Styled-componets:
componentWillMount() {
// subscribe to the event emitter. This
// is necessary due to pure components blocking
// context updates, this circumvents
// that by updating when an event is emitted.
const subscribe = this.context[CHANNEL];
this.unsubscribe = subscribe(nextTheme => { <----- MAGIC
React-redux:
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe =
this.store.subscribe(this.handleChange); <----- MAGIC
}
}
componentDidMount() {
this.trySubscribe();
}
Thus, even if parent Pure Component will block any update enables you to catch a change, store update, context variable change, or everything else.
So — something inside pure components is very soiled and absolutely impure. It is driven by side effects!
But this bypass straight logic flow, and works just differently from the rest of application.
So — just be careful. And don’t forget about magic.
Aaaand….
And this is a reason, why any redux store update will cause redraw in each connected component, and why you should use reselect just next to connect HoC —
to stop unnecessary change propagation.
But you should read this from another point of view:
redux-connect is a source of a change propagation.
redux connect is the end of a change propagation. It is still PureComponent.
And this leads to quite handy thing — you can control change propagation with redux-connect only. Just create a boundaries for a change. Lets talk about this in another article.
Conclusion
Pure components keep your application fast. Sometimes — more predictable, but often — less predictable, as long they change the way application works.
Stateless components are not pure, and may run slower than PureComponents by any kind.
But… if you very wish to create a fast application with good user experience — you have to use Pure Component.
No choice. But, now — you know hidden truth, and knew some magic…
React recommends that ajax request be made in componentDidMount(), rather than in componentWillMount(). For more info on this, read this post.
Since you want to make ajax requests in componentDidMount(), you need a class. There are two ways of writing component definitions: functional component and the class component. Functional components are more concise, but you don't get component lifecycle methods like componentDidMount(). Think of it as just a render function that takes props as inputs and outputs DOMs (in JSX). To override those lifecycle methods, you need to define them as a class.
If you want to use Redux, and want to make ajax requests in a Redux action, you should import the action creator function (fetchUser(..) in your case) that makes the ajax request, and dispatch(fetchUser(..)) in componentDidMount(). connect(..)ed components get dispatch(..) function passed to it by Redux store.
If you want to see how it's done in other redux apps, see the official example apps in the redux.js repo, paying attention to actions and containers: https://github.com/reactjs/redux/tree/master/examples
In Your case you can continue with statefull components no wrong in that
,If you need to go with functional way
There is a work arround
https://github.com/mobxjs/mobx/issues/162
Suggestion
Calling the api in componentDidMount will make sense than
componentWillMount , Because you can show the user something is
fetching.
I think,User component is designed nicely.It will act as a container for Profile to provide the Data.
Instead of making Profile component class oriented,it should be Stateless.
Lets User component pass the required data for Profile component.
You don't need to connect Profile component using redux-connect.Just render it as a Child component of User.
Profile
const Profile = (props) => {
const {user, likeProfile} = props;
//likeProfile()//call like this using dom event or programmatically.
return (
<h1>{user.profile.name}</h1>
)
}
You need to make some changes in User component.
Get the state for Profile component via mapStateToProps.
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
const {user, likeProfile} = this.props;
return (
<Profile user= {user} likeProfile={likeProfile} /> //passed the user data to Profile component vua User
)
}
Map the user state for Profile in User connect.
const mapStateToProps = (state)=>{
return{
user : state.somereducerkey.user //this will be accessible in Profile via props { user}
}
}
export default connect(mapStateToProps, {fetchUser, likeProfile})(User);
Say I have a top most smart component called Forecast that looks like this:
function mapStateToProps(state) {
return {
dates: state.getIn(['forecast', 'dates']),
isFetching: state.getIn(['forecast', 'isFetching'])
};
}
export default connect(mapStateToProps, {
fetchForecast
})(Forecast));
Which wraps a Forecast component like this:
import { getSummary, getDayForecast } from '../selectors/selectors';
export default class Forecast extends Component {
render() {
const { dates, isFetching } = this.props;
return (
<div className="row">
{dates.map(date => (
<Weather
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
</div>
);
}
};
Here I am passing 2 selectors as props into a Weather component. The selectors look like this:
import { createSelector } from 'reselect';
import moment from 'moment';
import { fromJS } from 'immutable';
const getDay = (state, key) => state.getIn(['forecast', 'forecast']).find(x => x.get('id') === key);
export const getSummary = createSelector(
[getDay],
(day => {
const firstPeriod = day.get('periods').first();
return fromJS({
date: day.get('date'),
outlook: firstPeriod.get('outlook'),
icon: firstPeriod.get('icon')
});
})
);
export const getDayForecast = createSelector(
[getDay],
(day) => day.get('periods').map(period => fromJS({id: period.get('id') }))
);
I don't have to pass these selectors down as props, I could easily just reference them in the weather component but I am confused as to how I would use these selectors in the Weather component as the Weather component is also dumb and won't have any reference to state. I only want 1 container or smart component at the top which the child components call or get props passed down.
The only way I can see of making this work is to have an intermediatary WeatherContainer component that looks something like this:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Weather from '../components/Weather';
import { getSummary, getDayForecast } from '../selectors/selectors';
function mapStateToProps(state, ownProps) {
return {
summary: getSummary(state, ownProps.date),
detail: getDayForecast(state, ownProps.date)
};
}
export default(connect(mapStateToProps,{}))(Weather);
And I would call like this:
{dates.map(date => (
<WeatherContainer
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
This seems completely wrong to have to create a container component like this.
How can I make use of selectors in dumb components or how can I pass them down as props baring in mind that they also need reference to the state?
In your WeatherContainer mapStateToProps you use your selectors but you're still passing them down as props. This is not necessary.
Besides that, you should know that creating your container WeatherContainer is the right way to go about things. You should never give a selector to a component. They should always be used in mapStateToProps. React-Redux will reevaluate this when state changes and will tell React to update your components whenever the result is different. This is a very important point. If you just grab the state inside a component, whether using a selector or not, then React-Redux doesn't know you're using this data and won't we able to tell React to rerender when this data changes.
Now, a lot of people are confused on this matter. There are dumb components, which just display stuff, and container components, which do stuff, like make API calls or implement functionality of sorts. But when you take a dumb component and connect it to Redux, then this doesn't make for a smart or container component. It still only displays stuff. Even if you use mapDispatchToProps to feed it some event listeners, this still doesn't really make the component smart. It could become smart if it contains significant code in mapStateToProps or mapDispatchToProps I guess. But such is life. The line between these things is just blurry.
The Redux Way is to connect everything that needs data. You can certainly pass data down to children, just as in plain React, but you create a more performant app by connecting components. Still, it's up to you to decide. But it is still important that anywhere you grab data from the store, it should be put inside a mapStateToProps so React-Redux can keep an eye on the data. You can safely pass it from a parent to a child as long as the data came from mapStateToProps.
This means passing selectors to children is a no-no. Also, where's the child going to get the state to pass as a parameter to the selectors? It doesn't work well so it's not a good idea. Note that whenever you connect a component, you're not creating an entirely new component. Is just a simple wrapper. It should contain very little code in very few lines. This should not give you pause. Just go for it. connect those components.
I should also mention that you can connect your Weather component directly inside the weather.js file. Unless you're going to reuse it, there's not much need to keep the unconnected component around. For testing you can export the unconnected component with a named export. If later on you decide you need to reuse theWeather component, you can always easily separate the component and the connect call into separate files.
I'm building an app that contains a store with an "offers" section of the state tree (ImmutableJS List Object). I need to take some action (play a browser sound) whenever an item is added to this list. Items can be added to this list via several different types of Redux actions.
I am trying to figure out the best way to react to the changes to a particular part of the store. I could do it in each action/reducer method, but then I would have it all over the place. I'd rather have one central place to handle the logic.
What's the best way to handle this? Should I create a generic store subscriber and has it's own logic for keeping track of the list values?
In this case your best bet is a store listener. Either a plain listener function or a redux connected React component.
Assuming a simple function to make noise:
function playSound () {
const audio = new Audio('audio_file.mp3')
audio.play()
}
You can create a store observer and listen for changes:
function createSoundObserver (store) {
let prevState = store.getState()
return store.subscribe(() => {
const nextState = store.getState()
if (prevState.messages.length < nextState.messages.length) {
playSound()
}
prevState = nextState
})
}
You can achieve the same with a React component:
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
class Notifier extends Component {
static propTypes = {
messages: PropTypes.array.isRequired
}
componentDidUpdate (prevProps) {
if (this.props.messages.length > prevProps.messages.length) {
playSound()
}
}
render () { return null }
}
export default connect((state, props) => {
const {messages} = state
return {messages}
}, {})(Notifier)
As long as a Notifier is present amongst the rendered tree, it will check for changes and play the sound accordingly. The advantage of this approach is that you don't have to take extra care of unsubscribing the event if you want to stay quiet, and it seamlessly works server-side rendering.
Imagine you have a relatively simple component you create as part of a component library (simplified for brevity):
class ExampleComponent extends React.Component {
componentDidMount() {
getAsyncData().then((response) => {
const {a} = response.data;
this.setState({a});
this.props.notify({a});
});
}
render() {
return (
<h1>{this.state.a}</h1>
);
}
}
The component is required to allow dropping it into an application (think Google Maps for relatively similar approach) and have it just work. It can, however, share its data from response with the rest of the application, via some sort of callback (see this.props.notify above) it may receive via its props. This is an actual requirement and not a architectural choice.
Since this is a part of a library - you don't know what kind of application it is going to get used in at all times, but you do know that in many many cases it is going to get used in a Redux application.
For Redux application the above self-contained approach is not necessarily the best - as the retrieved data in response is better kept in application state in Redux store, where it can be consumed by other parts of application.
Even more so - the ExampleComponent itself is better off being "passive" and not having state at all, rather using mapStateToProps to have Redux infrastructure inject the state update into it via props.
The idea is that when ExampleComponent is in Redux application - its setState call and reference to this.state in its render method are somehow abstracted and "re-routed" to props via Redux?
One way would be to make ExampleComponent to use dispatch that by default calls setState and can be overridden by injected Redux dispatch - basically take this to Redux:
class ExampleComponent extends React.Component {
constructor() {
super();
this.dispatch = this.props.dispatch || this.dispatch;
}
componentDidMount() {
getAsyncData().then((response) => {
this.dispatch({type: 'SOME_ACTION', data: response.data});
});
}
dispatch(action) {
swtich (action.type) {
case 'SOME_ACTION':
const {a} = action.data;
this.setState({a});
case 'ANOTHER_ACTION': ...
}
}
render() {
return (
<h1>{this.state.a}</h1>
);
}
}
The above example works very well, save for:
this.state.a and its kin being sprinkled around the code whereas in Redux it should be this.props.state
having to do this.dispatch = this.props.dispatch || this.dispatch; in every component
I would like to avoid the obvious BaseComponent solutions that would abstract setState into some kind of hybrid... as this would take the code, with time, further away from "canonical" React.
Do you see an elegant way where the two approaches can be combined, with Redux superseding the inherent one?
You're making a fundamental mistake in thinking that a React component with Redux is different from a React component without Redux.
In fact, a React component is just a React component.
This is all your component needs to look like:
function ExampleComponent({ a }) {
return (
<h1>{a}</h1>
);
}
Simple, clean, readable, testable.
There's no obvious reason why your asynchronous data fetch should be buried inside the component's componentDidMount() method. It can be triggered anywhere else in the application. And it should be.