Evil things you do with redux — dispatch in updating lifecycle methods

Written by ctlevi | Published 2017/07/22
Tech Story Tags: react | redux | web-development

TLDRvia the TL;DR App

In the code base I work on, we are doing an evil thing. Something that shouldn’t be done. We are making dispatch calls in React lifecycle methods such as componentWillReceiveProps, componentWillUpdate, and componentDidUpdate.

Here is one such scenario. We have two different components that are accessing two different pieces of state.

class Address extends React.Component {render() {return <input value={this.props.address} onChange={this.props.changeAddress}/>;}}

function mapStateToProps(state) {return {address: state.address}}

function mapDispatchToProps(dispatch) {return {changeAddress: (address) => dispatch(changeAddress(address)),}}

connect(mapStateToProps, mapDispatchToProps)(Address);

class Zipcode extends React.Component {render() {return <input value={this.props.zipcode} onChange={this.props.changeZipcode}/>;}}

function mapStateToProps(state) {return {zipcode: state.zipcode}}

function mapDispatchToProps(dispatch) {return {changeZipcode: (zipcode) => dispatch(changeZipcode(zipcode)),}}

connect(mapStateToProps, mapDispatchToProps)(Zipcode);

Now suppose we want to add an alert to the address component if the user enters an address that doesn’t appear to be in the zip code they enter. We’re going to store this alert data in the redux state. There are many ways we could do that. But the solution would need to check the address when either (1) the address changed or (2) the zip code changed.

Our application uses redux-saga, so these solutions are from that perspective. I am also assuming we would need to make some network call to do the actual checking. Thus we are going to need to do this work in some async middleware.

Solution 1

We dispatch another action when changeZipcode or changeAddress is called. For instance, the mapDispatchToProps function of the Zipcode component would change to:

function mapDispatchToProps(dispatch) {return {changeZipcode: (zipcode) => {dispatch(changeZipcode(zipcode));dispatch(checkAddress());}}}

Then we have a saga waiting for the checkAddress action to do the actual check of the address against the zipcode and update some state for us to render in the Address component.

I don’t like this solution very much because both changeZipcode and changeAddress would need this exact same logic.

Solution 2

Maybe we just have a saga waiting for changeZipcode or changeAddress actions and do the check when those happen. Now we don’t need a checkAddress action.

Solution NEVER DO THIS

Well here is how we do it. Zipcode would not change at all. Instead we add a componentWillReceiveProps on Address that checks whether address or zipcode changed. If so, dispatch another action to check the address.

class Address extends React.Component {componentWillReceiveProps(nextProps) {if (this.props.zipcode !== nextProps.zipCode || this.props.address !== nextProps.address) {this.props.checkAddress();}}

render() {return (<div><p>{this.props.addressAlert}</p><input value={this.props.address} onChange={this.props.changeAddress}/>;</div>);}}

function mapStateToProps(state) {return {address: state.address,addressAlert: state.addressAlert,zipcode: state.zipcode,}}

function mapDispatchToProps(dispatch) {return {changeAddress: (address) => dispatch(changeAddress(address)),checkAddress: () => dispatch(checkAddress()),}}

connect(mapStateToProps, mapDispatchToProps)(Address);

I felt a chill when I first saw this. I looked up and asked why many times. It felt so wrong. It makes the component know way more than it should. Why does Address care about zipcode again??

This might not seem so bad. In fact, you may be saying it’s perfectly fine. Address cares about zipcode because it needs to update an address alert when zipcode changes. But here’s the thing. Your components should not be thinking about external state transitions. That concern is in the land of redux and only there.

Potential problems

To illustrate my point a bit more here are some potential problems. For instance, you may have a very hot piece of state that when changed, a lot of your application needs to change. You would need to add that piece of state to every component that cares about it and then write that componentWillReceiveProps check. How do you keep two components from dispatching the same action when they see that piece of state change?

Or even worse, you can have a cascade of renders.

  1. Component A does first dispatch
  2. Component B watches for change of state from dispatch in Component A and does second dispatch
  3. Component C watches for change of state from dispatch in Component B and does third dispatch
  4. And so on and so on.

Your application can go through these multi step transformations, but it’s really difficult to trace back that first action that kicked off the whole transformation.

One last thing. I’m not saying these updating lifecycle methods are bad. You absolutely need them for when your component has its own internal state. Or when you need to do some imperative DOM work when interfacing with a non react library. But they should never be for kicking off other changes to your redux state in response to other redux events.

In conclusion, make your components as simple as possible. The best component is simply a function of it’s props. And if you ever see a dispatch call in updating lifecycle methods like componentWillReceiveProps, componentWillUpdate, and componentDidUpdate there is most definitely a better way.


Published by HackerNoon on 2017/07/22