20th Nov 2019

Redux Standard Props

Components connected to Redux have three types of props:

  1. values that derive from reducers
  2. functions that dispatch actions
  3. values/functions that make up a components public API

The props passed to the Films component below best illustrates this:


const Films = ({
  genre,
  films,
  hasRecommendToFriend,
  onWatchLater,
  onPlay
}) => {
  const [film, setFilm] = useState("");
  const handleFilm = ({ target: { value } }) => setFilm(value);

  const handleSubmit = event => {
    event.preventDefault();

    onWatchLater(film);
  };

  return (
    <form onSubmit={handleSubmit}/>
      <h3/>{genre}</h3/>

      <select value={film} onChange={handleFilm}/>
        {films.map(({ id, title }) => (
          <option key={id} value={id}/>
            {title}
          </option/>
        ))}
      </select/>

      {hasRecommendToFriend && film && <RecommendToFriend film={film} />}

      <button type="button" onClick={onPlay}/>
        Play
      </button/>
      <button/>Watch Later</button/>
    </form/>
  );
};

const mapStateToProps = ({ genre, films }) => ({
  genre,
  films
});

const mapDispatchToProps = dispatch => ({
  onWatchLater: film => dispatch(watchLater(film))
});

One thing the different types of props lack is an identity.

It is difficult to understand where each prop comes from without digging into the code. Is genre passed from a parent component or does it derive from a reducer? Is onWatchLater a prop callback or an action?

A quick scan of mapStateToProps and mapDispatchToProps reveals prop types 1 and 2, which subsequently surfaces prop type 3 (by process of elimination) however there's a fair amount of flip-flopping involved.

This can be remedied by grouping Redux specific props under a dedicated namespace:


store: {
  // holds values derived from reducers
},
actions: {
  // holds functions that dispatch actions
}

Below is a revised version of the Films component using this approach:


const Films = ({
  store,
  actions,
  hasRecommendToFriend,
  onPlay
}) => {
  const [film, setFilm] = useState("");
  const handleFilm = ({ target: { value } }) => setFilm(value);

  const handleSubmit = event => {
    event.preventDefault();

    actions.onWatchLater(film);
  };

  return (
    <form onSubmit={handleSubmit}/>
      <h3/>{store.genre}</h3/>

      <select value={film} onChange={handleFilm}/>
        {store.films.map(({ id, title }) => (
          <option key={id} value={id}/>
            {title}
          </option/>
        ))}
      </select/>

      {hasRecommendToFriend && film && <RecommendToFriend film={film} />}

      <button type="button" onClick={onPlay}/>
        Play
      </button/>
      <button/>Watch Later</button/>
    </form/>
  );
};

const mapStateToProps = ({ genre, films }) => ({
  store: {
    genre,
    films
  }
});

const mapDispatchToProps = dispatch => ({
  actions: {
    onWatchLater: film => dispatch(watchLater(film))
  }
});

Now it is explicitly clear that store.films comes from Redux state and actions.onWatchLater dispatches an action. All other props (those not within the store and actions namespace) make up the components public API, which are usually the props consumers care most about.

Never Miss a Post!

If you have enjoyed this blog post I invite you to join my newsletter:

Emails are kept safe with MailChimp And I never ever send spammy emails (Or stay updated with RSS)