Clearing specific redux state before rendering component

3.2k views Asked by At

I have the following "Buy" button for a shopping cart.

I also have a component called Tooltip, which will display itself for error/success messages. It uses the button's width to determine it's centre point. Hence, I use a `ref since I need to access it's physical size within the DOM. I've read that it's bad news to use a ref attribute, but I'm not sure how else to go about doing the positioning of a child component that is based off the physical DOM. But that's another question... ;)

I am persisting the app's state in localStorage. As seen here: https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage

The issue I'm running into is that I have to clear the state's success property before rendering. Otherwise, if I have a success message in the state, on the initial render() the Tooltip will attempt to render as well. This won't be possible since the button it relies on is not yet in the DOM.

I thought that clearing the success state via Redux action in componentWillMount would clear up the success state and therefore clear up the issue, but it appears that the render() method doesn't recognize that the state has been changed and will still show the old value in console.log().

My work-around is to check if the button exists as well as the success message: showSuccessTooltip && this.addBtn

Why does render() not recognize the componentWillMount() state change?

Here is the ProductBuyBtn.js class:

import React, { Component } from 'react';
import { connect } from 'react-redux'

// Components
import Tooltip from './../utils/Tooltip'

// CSS
import './../../css/button.css'

// State
import { addToCart, clearSuccess } from './../../store/actions/cart'

class ProductBuyBtn extends Component {

 componentWillMount(){
  this.props.clearSuccess()
 }

 addToCart(){
  this.props.addToCart(process.env.REACT_APP_SITE_KEY, this.props.product.id, this.props.quantity)
 }

 render() {

  let showErrorTooltip = this.props.error !== undefined
  let showSuccessTooltip = this.props.success !== undefined

  console.log(this.props.success)

  return (
   <div className="btn_container">
    <button className="btn buy_btn" ref={(addBtn) => this.addBtn = addBtn } onClick={() => this.addToCart()}>Add</button>
    {showErrorTooltip && this.addBtn &&
     <Tooltip parent={this.addBtn} type={'dialog--error'} messageObjects={this.props.error} />
    }
    {showSuccessTooltip && this.addBtn &&
     <Tooltip parent={this.addBtn} type={'dialog--success'} messageObjects={{ success: this.props.success }} />
    }
   </div>
  );
 }
}

function mapStateToProps(state){
 return {
  inProcess: state.cart.inProcess,
  error: state.cart.error,
  success: state.cart.success
 }
}

const mapDispatchToProps = (dispatch) => {
 return {
  addToCart: (siteKey, product_id, quantity) => dispatch(addToCart(siteKey, product_id, quantity)),
  clearSuccess: () => dispatch(clearSuccess())
 }
}

export default connect(mapStateToProps, mapDispatchToProps)(ProductBuyBtn)

1

There are 1 answers

5
jonahe On BEST ANSWER

Well, it seems to be a known problem that's easy to get into (harder to get out of, especially in a nice / non-hacky way. See this super-long thread).

The problem is that dispatching an action in componentWillMount that (eventually) changes the props going in to a component does not guarantee that the action has taken place before the first render.

So basically the render() doesn't wait for your dispatched action to take effect, it renders once (with the old props), then the action takes effect and changes the props and then the component re-renders with the new props.

So you either have to do what you already do, or use the components internal state to keep track of whether it's the first render or not, something like this comment. There are more suggestions outlined, but I can't list them all.