dagda1
dagda1

Reputation: 28810

Using selectors with smart/dumb components in redux

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?

Upvotes: 2

Views: 3086

Answers (1)

DDS
DDS

Reputation: 4375

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.

Upvotes: 6

Related Questions