【问题标题】:Redux not Re-rendering React components even though store is updated即使存储已更新,Redux 也不会重新渲染 React 组件
【发布时间】:2017-12-29 07:12:04
【问题描述】:

您好,我是 Redux 的新手,我正在使用 React 和 Redux 尝试构建一个 UI,我可以在其中将文件(在本例中为发票)拖放到 UI 的一部分中,将它们呈现在列表中并然后能够启动一个弹出窗口来编辑与每张发票关联的元数据。拖放一切正常 - 每次删除文件并更新列表时,Redux 都会重新渲染视图。但是,当我尝试单击每张发票的编辑按钮时,商店正在更新,但我的弹出框组件中的道具没有。事实上,当我尝试点击编辑发票按钮时,看起来根本没有发生任何重新渲染

App.js

import React from 'react'
import AddInvoice from '../containers/AddInvoice'
import CurrentInvoiceList from '../containers/CurrentInvoiceList'
import ControlPopover from '../containers/ControlPopover'

const App = () => (
  <div>
    <AddInvoice />
    <CurrentInvoiceList />
    <ControlPopover />
  </div>
)

export default App

容器/AddInvoice.js

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

const recipientDataDefaults = {
  name: '',
  surname: '',
  address: '',
  phone: ''
};

const handleDragOver = event => {
  event.stopPropagation();
  event.preventDefault();
  event.dataTransfer.dropEffect = 'copy';
};

const handleDragEnter = event => {
  event.stopPropagation();
  event.preventDefault();
};

const handleDragLeave = event => {
  event.stopPropagation();
  event.preventDefault();    
};

let AddInvoice = ({ dispatch }) =>
  const styles = {'minHeight': '200px', 'background': 'tomato'}
  return (
    <div style={styles}
         onDragEnter={handleDragEnter} 
         onDragLeave={handleDragLeave} 
         onDragOver={handleDragOver} 
         onDrop={event => {
           event.stopPropagation(); 
           event.preventDefault();
           const data  = event.dataTransfer;
           const files = data.files;

           const newInvoiceUploads = Object.keys(files)
             .map(key => files[key])
             .map(file => {
               const invoiceObject = {};
               invoiceObject.files = [file];
               invoiceObject.recipientData = Object.assign({}, recipientDataDefaults);
               return invoiceObject;
             });

           newInvoiceUploads.forEach(invoice => dispatch(addInvoice(invoice)))

    }}>
      Drag an invoice here to upload
    </div>
  )
}
AddInvoice = connect()(AddInvoice)

export default AddInvoice

容器/ControlPopover.js

import { connect } from 'react-redux'
import { closePopoverWithoutSave } from '../actions'
import Popover from '../components/Popover/Popover'

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

const mapDispatchToProps = {
  handleCancel: closePopoverWithoutSave
}


const ControlPopover = connect(
  mapStateToProps,
  mapDispatchToProps
)(Popover)

export default ControlPopover

容器/CurrentInvoiceList.js

import { connect } from 'react-redux'
import { showInvoiceEditPopover } from '../actions'
import InvoiceList from '../components/InvoiceList/InvoiceList'

const mapStateToProps = state => {
  return {
    invoices: state.invoices
  }
}

const mapDispatchToProps = dispatch => ({
  handleEditInvoice: invoice => {
    dispatch(showInvoiceEditPopover(invoice))
  }
})

const CurrentInvoiceList = connect(
  mapStateToProps,
  mapDispatchToProps
)(InvoiceList)

export default CurrentInvoiceList

actions/index.js

let nextInvoiceId = 0
export const addInvoice = invoice => ({
  type: 'ADD_INVOICE',
  id: nextInvoiceId++,
  invoiceData: invoice
})

export const showInvoiceEditPopover = invoice => ({
  type: 'SHOW_POPOVER',
  invoice
})

popover reducer(结合在应用程序中,但为了简洁起见在这里内联)reducers/index.js

const popover = (state = {}, action) => {
  switch (action.type) {
    case 'SHOW_POPOVER':
      const popoverState = {}
      popoverState.isActive = true
      popoverState.data = action.invoice
      return popoverState

     case 'CLOSE_POPOVER_WITHOUT_SAVING':
       const inactiveState = {}
       inactiveState.isActive = false
       inactiveState.data = {}
       return inactiveState;
     default:
       return state
   }
}

export default popover

组件/InvoiceList.js

import React from 'react'
import PropTypes from 'prop-types'
import Invoice from '../Invoice/Invoice'

const InvoiceList = ({ invoices, handleEditInvoice }) => {

return (
  <div>
    {invoices.map(invoice =>
      <Invoice
        key={invoice.id}
        invoice={invoice.invoiceData}
        onClick={event => {
          // here we invoke the action bound by the CurrentInvoiceList
          // container
          event.preventDefault()
          handleEditInvoice(invoice)
        }}
      />
    )}
  </div>
 )
}

InvoiceList.propTypes = {
  invoices: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    invoiceData: PropTypes.object
  }).isRequired).isRequired,
  handleEditInvoice: PropTypes.func.isRequired
}

export default InvoiceList

组件/Invoice.js

import React from 'react'
import PropTypes from 'prop-types'
import TextInput from '../TextInput/TextInput';
import Button from '../Button/Button';
import './invoice.css'

const Invoice = ({ invoice, onClick }) => {

const fileNames = invoice.files.map((file, index) => {
    return (<div key={index} className="invoice__file-title-legend">
   {file.name}</div>);
  });

return (
    <div className="invoice">
    <form className="invoice__form">
    <TextInput id="invoice__input-amount" placeholder="enter invoice amount" label="Invoice Amount" />
    <TextInput id="invoice__input-target" placeholder="enter payment target" label="Payment Target" />
    <Button value="Add recipient" onClick={onClick} /> // clicking this button updates the store but does NOT re-render. Why?
  </form>

  <div className="invoice__files">{fileNames}</div>
</div>
  )
}

Invoice.propTypes = {
  onClick: PropTypes.func.isRequired,
  invoice: PropTypes.object
}

export default Invoice

组件/Popover/Popover.js

import React from 'react'
import PropTypes from 'prop-types'
import createModifiers      from '../../lib/createModifiers';
import './Popover.css';

const Popover = ({ handleCancel, isActive }) => {
  console.log('rendering popover component') // does not get called when invoice edit button is pressed
  const popoverModifiers = createModifiers('popover', {
    'is-active': isActive
  })

  return (
    <div className={popoverModifiers}>
      <div className="popover__header">
        <button onClick={handleCancel}>x</button>
      </div>
      <div className="popover__content">
       Popover content
      </div>
    </div>
  )
}
Popover.propTypes = {
  handleCancel: PropTypes.func.isRequired,
  isActive: PropTypes.bool.isRequired 
}

export default Popover

最后是后代的 createModifiers 代码。这段代码只是根据传入的状态布尔值生成一些 BEM 修饰符 CSS 类

const createModifiers = (element, modifiers) => {
  const classList = Object.keys(modifiers)
    .filter(key => modifiers[key])
    .map(modifier => `${element}--${modifier}`)
    .concat(element)
    .join(' ')

  return classList
}

export default createModifiers

我知道这是大量的示例代码,所以我尽量保持简短和重点突出,同时提供应用程序的全面视图。非常感谢任何帮助。

【问题讨论】:

  • Control Popover 的 mapStateToProps 是否被调用?
  • 另外,您在 ControlPopover 中的 mapDispatchToProps 看起来很奇怪。我相信您需要对其进行修改以返回类似 mapStateToProps 的函数。这可能会引发错误,导致状态更新无法传播。
  • mapStateToProps 被调用,但我绑定到了错误的对象属性

标签: reactjs redux react-redux redux-form


【解决方案1】:

问题出在 container/ControlPopover.js 和 mapStateToProps 函数中。 isActive 属性需要分配给state.popover.isActive

【讨论】:

    【解决方案2】:

    我相信您的问题是您的 mapDispatchToProp 函数格式不正确。

    您需要返回一个具有方法的对象。这些方法将作为道具提供给您的连接组件。

    例子:

    const mapDispatchToProps = ( dispatch ) => {
        return {
            doSomething: ( arguments ) => {
                // here you can dispatch and use your arguments
            } 
        };
    }
    

    doSomething 是提供给连接组件的道具。

    您的所有 mapDispatchToProps 函数格式不正确。

    旁注/意见 - TLDR

    以后如果你有很多代码要贴,我相信如果把这些部分链接在一起会更容易消化。

    I.E.

    // App.js
    const App = () => (
        <div>
            <Header />
            <Body />
            <Footer />
        </div>
    );
    

    组件按以下顺序出现:页眉 -> 正文 -> 页脚。按顺序为它们提供代码,将它们的操作、reducer、表示和容器信息放在一个块中。

    标题

    // header.presentational.js ...
    
    // header.container.js ... ( or where you mapStateToProps and connect )
    
    // header.actions.js ...
    
    // header.reducer.js ...
    

    身体...

    页脚 ...

    我不知道你的代码是否不同,但你的mapStateToDispatch函数格式仍然不正确。

    改变这个...

    const mapDispatchToProps = dispatch => ({
      handleEditInvoice: invoice => {
        dispatch(showInvoiceEditPopover(invoice))
      }
    })
    

    到这里:

    const mapDispatchToProps = dispatch => ({
      return {
        handleEditInvoice: invoice => {
          dispatch(showInvoiceEditPopover(invoice))
        }
      };
    })
    

    【讨论】:

    • 感谢您的回答以及有关格式的反馈。 const mapDispatchToProps = { handleCancel: closePopoverWithoutSave } 似乎格式错误,是的。不过,直到弹出窗口打开后才会调用它。无论如何,修复此功能并不能解决问题。 Popover 组件仍未呈现。
    • 请您将代码发布到您的createModifiers
    • 在上面发布了 createModifiers。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-15
    • 2020-02-18
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-22
    相关资源
    最近更新 更多