简体   繁体   中英

Child component of a connected component using redux connect doesn't re-render after one prop change

I have the main navigation menu of my app as a react component connected to a redux store using connect. The menu component is also structured into sections, and inside sections there are topics. The menu has a status variables (in the store) that indicates if the menu must appear collapsed or not. I change the status with the corresponding action and I see that the menu appearance change, but the sections appearance don't change. I pass the value of collapse as a prop but if doesn't call to the render function of the menu sections to be render accordingly.

Here is the code of the Menu and MenuSection components:

Menus.jsx

// modules/menu/components/Menu.jsx

import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as actions from '../actions'

import { MODULE_NAME } from '../constants'
import MenuSection from './MenuSection'

const propTypes = {
  menu: PropTypes.shape({
    sections: PropTypes.array,
    collapsed: PropTypes.bool,
    activeSection: PropTypes.number,
    inflatedSection: PropTypes.number,
    activeTopic: PropTypes.number,
  }).isRequired,
  actions: PropTypes.shape({
    toggle: PropTypes.func,
    activateSection: PropTypes.func,
    toggleSection: PropTypes.func,
  }).isRequired,
}

class Menu extends React.Component {

  constructor(props) {
    super(props)
  }

  toggleMenu = () => this.props.actions.toggleMenu()

  activateSection = (s, t) => this.props.actions.activateSection(s, t)

  toggleSection = (s) => this.props.actions.toggleSection(s)

  render() {
    const collapsed = this.props.menu.collapsed
    const sections = this.props.menu.sections.map((section, i) => {
      const {
        disabled = false,
        icon, text,
        route = { to: '#' },
        topics = [],
      } = section
      const active = this.props.menu.activeSection === i
      const inflated = this.props.menu.inflatedSection === i
      return (
        <MenuSection
          key={i}
          idx={i}
          to={route.to}
          {...{
            icon,
            text,
            topics,
            disabled,
            active,
            inflated,
          }}
          activate={this.activateSection}
          toggle={this.toggleSection}
          activeTopic={this.props.menu.activeTopic}
          collapsed={collapsed}
        />
      )
    })
    const classes = classNames('menu', { collapsed: collapsed })
    const size = collapsed ? 'small' : 'big' 
    return (
      <nav className={classes}>
        <img src={`${process.env.PUBLIC_URL}/imgs/logo-${size}.png`} />
        <ul>
          {sections}
        </ul>
      </nav>
    )
  }

}

Menu.propTypes = propTypes

const mapStateToProps = state => ({ [MODULE_NAME]: state.menu })

const mapDispatchToProps = dispatch => (
  { actions: bindActionCreators(actions, dispatch) }
)

export default connect(mapStateToProps, mapDispatchToProps)(Menu)

MenuSection.jsx

    // modules/menu/components/MenuSection.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { NavLink, withRouter } from 'react-router-dom'
import classNames from 'classnames'

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCaretDown, faCaretUp } from '@fortawesome/free-solid-svg-icons'

import MenuTopic from './MenuTopic'

const propTypes = {
  idx: PropTypes.number.isRequired,
  to: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired,
  icon: PropTypes.object.isRequired,
  text: PropTypes.string.isRequired,
  topics: PropTypes.arrayOf(PropTypes.object).isRequired,
  activate: PropTypes.func.isRequired,
  toggle: PropTypes.func.isRequired,
  disabled: PropTypes.bool.isRequired,
  active: PropTypes.bool.isRequired,
  inflated: PropTypes.bool.isRequired,
  activeTopic: PropTypes.number.isRequired,
  collapsed: PropTypes.bool.isRequired
}

class MenuSection extends Component {

  constructor(props) {
    super(props)
    this.onClick = this.onClick.bind(this)
    this.activate = this.activate.bind(this)
  }

  shouldComponentUpdate(nextProps) {
    const { active, inflated, activeTopic } = nextProps;
    return (this.props.active !== active)
      || (this.props.inflated !== inflated) || (this.props.activeTopic !== activeTopic)
  }

  onClick() {
    if (this.props.topics.length > 0) {
      this.props.toggle(this.props.idx)
    } else {
      this.props.activate(this.props.idx, this.props.activeTopic)
    }
  }

  activate(t) {
    this.props.activate(this.props.idx, t)
  }

  render() {
    const { idx, to, icon, text, topics, disabled, active, inflated, activeTopic, collapsed } = this.props
    const items = (inflated ?
      topics.map((topic, i) => (
        <MenuTopic key={i} idx={i} 
          to={topic.route.to} text={topic.text} onClick={this.activate}
          active={active && (activeTopic === i)} section={idx} />))
      : []);
    const classes = classNames({ disabled, active, inflated })
    return (
      <li className={classes}>
        <NavLink to={to} activeClassName="active" onClick={this.onClick}>
          <FontAwesomeIcon icon={icon}  pull="left"/>
          {!collapsed &&
            <React.Fragment>
              {' '}
              {text}
              {topics.length > 0 &&
                <FontAwesomeIcon icon={ inflated ? faCaretUp : faCaretDown } pull="right" />
              }
            </React.Fragment>
          }
        </NavLink>
        {(!collapsed) && inflated &&
          <ul>
            {items}
          </ul>
        }
      </li>)
  }

}

MenuSection.propTypes = propTypes

export default withRouter(MenuSection)

Any idea of what is going on? I'm a little bit new with react-redux apps so maybe I'm missing something.

Thanks in advance!

It looks like the reason it's not rerendering is because of menuSection's shouldComponentUpdate. As written, the menuSection will only rerender if props.active, props.inflated, or props.activeTopic changes. So if props.collapsed changes but nothing else does, then render is skipped.

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