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.