简体   繁体   中英

prevProps and this.props will always return same value in componentDidUpdate when using react-redux

When a user clicks Log in link in navigation, the page shows a modal. The navbar and modal is contained in AppHeader component. The other component called ActionBar has a login button too. So I used react-redux to communicate between different components. Here is my implementation. ( store.js is omitted.)

actionTypes.js

export const LOGIN_FORM_SHOW = 'LOGIN_FORM_SHOW'

actions.js

import { LOGIN_FORM_SHOW } from './actionTypes'
export const loginFormShow = () => ({
   type: LOGIN_FORM_SHOW,
   payload: {}
})

reducer.js

import { LOGIN_FORM_SHOW } from './actionTypes'
const defaultState = { 
  loginFormVisible: false 
}
export default function(state = defaultState, action) {
  switch(action.type) {
    case LOGIN_FORM_SHOW: {
      return { 
        ...state,
        loginFormVisible: true 
      }
    }
  }
}

AppHeader.js

import React from 'react'
import LoginModal from '../components/LoginModal'
import {authService} from 'services/auth'
import { connect } from 'react-redux'
import {loginFormShow} from "../../redux/actions"

class AppHeader extends React.PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      loginFormVisible: this.props.loginFormVisible ? true : false
    }
    this.showLoginModal = this.showLoginModal.bind(this)
  }
  showLoginModal() {
    this.setState({
      loginFormVisible: true
    })
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    if(this.props.loginFormVisible) {
      this.showLoginModal()
    }
  }
  render() {
    return (
      <>
        // ... rest omitted
        <nav><ul>
          <li onClick={this.showLoginModal}>Login</li>
        </ul></nav>
        // ... ... ...
        <LoginModal 
          open = {this.state.loginFormVisible}
          onClose = { () => { this.setState(loginFormVisible: false) } }
        />  
      </>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    loginFormVisible: state.loginFormVisible
  }
}

export default connect(
  mapStateToProps, 
  null
)(AppHeader)

ActionBar.js

import React from 'react'
import {loginFormShow} from "../../redux/actions"
import {connect} from "react-redux"

class ActionBar extends React.Component{
  render() {
    return (
      <button onClick={this.props.loginFormShow}>Log In</button>
    )
  }
}

export default connect(
  null,
  { loginFormShow }
)(ActionBar)

Log in button in AppHeader works fine. But button in ActionBar shows a problem.

When a login modal is shown after button in ActionBar has been clicked. You cannot close login modal because componentDidUpdate method will be called and props.loginFormVisible will always return true .

Since react-redux delivers store state as props, there is no way you can modify props. So you cannot tell whether request from ActionBar has been actually handled and it's OK to close login modal. prevProps and props will always be same after first button click in ActionBar .

I can define another actionType LOGIN_FORM_CLOSE and fix this issue but I think there lies some limitation of react-redux .

Without using actionType LOGIN_FORM_CLOSE, my workaround is as follows:

AppHeader.js

componentDidUpdate(prevProps, prevState, snapShot) {
  if((!prevProps.lastHandled || (this.props.lastHandled - prevProps.lastHandled)) && this.props.loginFormVisible) {
    this.showLoginModal()
  }
}
// ....
const mapStateToProps = (state) => {
  const props = { loginFormVisible: state.loginFormVisible }
  if (state.loginFormVisible) {
    props.lastHandled= (new Date).getTime()
  }
}

Why does react-redux only map store state to props?. You cannot access component's state in mapStateToProps . I find it hard to use.

You don't need your componentDidUpdate . You can conditionally render your component based on this.props.loginFormVisible

  render() {
    return (
      <>
        // ... rest omitted
        <nav><ul>
          <li onClick={this.showLoginModal}>Login</li>
        </ul></nav>
        // ... ... ...
        {this.props.loginFormVisible && <LoginModal 
          open = {this.state.loginFormVisible}
          onClose = { () => { this.setState(loginFormVisible: false) } }
        /> } 
      </>
    )
  }
}

This is a simple way to reduce interference from your componentDidUpdate . You may be able to get rid of your open prop as well, and simplify things.

Redux is great when trying to synchronize state between components. Because you're in a situation where you need to share state across at least 2 components, and both of those components need to be able to alter and/or read state, I would recommend keeping loginFormVisible in your store's state. I would read it as a prop in both components, and only alter it through actions. So making a LOGIN_FORM_CLOSE action is a great idea. Whenever you have a line like this.state = { loginFormVisible: this.props.loginFormVisible ? true : false } this.state = { loginFormVisible: this.props.loginFormVisible ? true : false } , you need to reconsider where you want your state to live. In your case, its probably better managed completely in the store for this piece of state.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM