React Redux - Uncaught TypeError:无法读取undefined的属性'setState'

时间:2017-06-10 13:06:49

标签: reactjs react-redux

新手。

我找到了答案herehere

我也在使用Redux。按照良好做法,我有一个容器“AddressContainer”及其组件“Address”。

AddressContainer如下 -

body {
  height:100vh;
  width:100vw;
  background:linear-gradient(160deg, red, red 60%, white 60%, white);
}

地址组件如下 -

import React, { Component, PropTypes } from 'react'
        import { connect } from 'react-redux'
        import { Field, change } from 'redux-form'
        import { Col, Panel, Row } from 'react-bootstrap'
        import Select from 'react-select'

        import Address from './address'

        import { ensureStateData, getSuburbs } from './actions'

        import { CLIENT_FORM_NAME } from '../clients/client/client'

        export class AddressContainer extends Component {
          static contextTypes = {
            _reduxForm: PropTypes.object.isRequired,
          }

          constructor(props, context) {
            super(props, context)

            this.state = {
              selectedSuburb: null,
            }
          }

          componentDidMount() {
            this.props.ensureStateData()
          }

          // Manage asyncSelect for new data request - for suburbs.
          handleSuburbSearch = (query) => {
            const { addressData } = this.props
            const companyStateId = addressData.companyStateId

            if (!query || query.trim().length < 2) {

              return Promise.resolve({ options: [] })
            }
            const queryString = {
              query: query,
              companyStateId: companyStateId,
            }
            return getSuburbs(queryString)
              .then(data => {
                return { options: data }
              })
          }


          render() {
            const {
              initialValues,
              addressData,
              updatePostcodeValue,
            } = this.props


            //const { value } = this.state
            const sectionPrefix = this.context._reduxForm.sectionPrefix


            if (addressData.isLoading || !addressData.states.length) {
              return (
                <p>Loading</p>
              )
            }
            if (addressData.error) {
              return (
                <p>Error loading data</p>
              )
            }


            const companyStateId = addressData.companyStateId
            //  initialValues = {
            //    ...initialValues.Address=null,
            //    state: addressData.states.find(option => option.stateId === companyStateId),
            //  }


            return (
              <Address 
                initialValues={initialValues}
                addressData={addressData}
                handleSuburbSearch={this.handleSuburbSearch}
              />
            )
          }
        }
        const mapStateToProps = (state) => ({
          initialValues: state.address,
          companyStateId: state.companyStateId,
          addressData: state.addressData,
        })

        const mapDispatchToProps = (dispatch) => ({
          ensureStateData: () => dispatch(ensureStateData()),
          getSuburbs: (values) => dispatch(getSuburbs(values)),
          updatePostcodeValue: (postcode, sectionPrefix) => dispatch(change(CLIENT_FORM_NAME, `${sectionPrefix ? (sectionPrefix + '.') : ''}postcode`, postcode))
        })

        export default connect(mapStateToProps, mapDispatchToProps)(AddressContainer)

问题在于下面的地址组件中的以下函数以及它所说的未定义的setState -

import React, { Component, PropTypes } from 'react'
      import { connect } from 'react-redux'
      import { Field, reduxForm, change } from 'redux-form'
      import { Col, Panel, Row } from 'react-bootstrap'
      import Select from 'react-select'
      import FormField from '../formComponents/formField'
      import TextField from '../formComponents/textField'
      import StaticText from '../formComponents/staticText'

      export const ADDRESS_FORM_NAME = "Address"

      export const Address = (props) => {
        const { addressData, handleSuburbSearch } = props
        const { reset } = props

        return (
          <Panel header={<h3>Client - Address Details</h3>}>
            <Row>

              <Field component={TextField}
                name="address1"
                id="address1"
                type="text"
                label="Address Line 1"
                placeholder="Enter street 1st line..."
                fieldCols={6}
                labelCols={3}
                controlCols={9}
              />
              <Field component={TextField}
                name="address2"
                id="address2"
                type="text"
                label="Address Line 2"
                placeholder="Enter street 2nd line..."
                fieldCols={6}
                labelCols={3}
                controlCols={9}
              />
            </Row>
            <Row>
              <Field
                component={props => {
                  const { input, id, placeholder, type } = props
                  const { fieldCols, labelCols, controlCols, label, inputClass } = props
                  // just the props we want the inner Select textbox to have
                  const { name, onChange } = input
                  const onStateChange = (state) => {
                    console.log('onStateChange', state)
                    onChange(state)
                  }

                  return (
                    <FormField
                      id={id}
                      label={label}
                      fieldCols={fieldCols}
                      labelCols={labelCols}
                      controlCols={controlCols}
                      inputClass={inputClass}
                    >
                      <Select
                        name={name}
                        onChange={onStateChange}
                        placeholder="Select state"
                        valueKey="id"
                        options={addressData.states}
                        labelKey="stateLabel"
                        optionRenderer={option => `${option.stateShortName} (${option.stateName})`}
                        value={input.value}
                        selectValue={Array.isArray(input.value) ? input.value : undefined}
                      />

                    </FormField>
                  )
                }}
                name="state"
                id="state"
                label="State."
                fieldCols={6}
                labelCols={3}
                controlCols={6}
              />

            </Row>
            <Row>
              <Field
                component={props => {
                  const { input, id, placeholder, type } = props
                  const { fieldCols, labelCols, controlCols, label, inputClass } = props
                  const { name, value, onChange, onBlur, onFocus } = input
                  const inputProps = {
                    name,
                    value,
                    onChange,
                    onBlur,
                    onFocus,
                  }
                  const onSuburbChange = (value) => {
                    console.log('onSuburbChange: ', value)
                    this.setState({ selectedSuburb: value }, () => {
                      input.onChange(value)
                      updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
                    })
                  }

                  return (
                    <FormField
                      id={id}
                      label={label}
                      fieldCols={fieldCols}
                      labelCols={labelCols}
                      controlCols={controlCols}
                      inputClass={inputClass}
                    >
                      <Select.Async
                        {...inputProps}
                        onChange={onSuburbChange}
                        valueKey="id"
                        labelKey="suburbName"
                        loadOptions={handleSuburbSearch}
                        backspaceRemoves={true}
                      />
                    </FormField>
                  )
                }}
                name="suburb"
                id="AddressLocation"
                label="Suburb."
                fieldCols={6}
                labelCols={3}
                controlCols={9}
              />

            </Row>
            <Row>
              <Field component={StaticText}
                name="postcode"
                id="postcode"
                label="Postcode."
                fieldCols={6}
                labelCols={3}
                controlCols={9}
              />

            </Row>
          </Panel>
        )

      }

      Address.propTypes = {
        handleSuburbSearch: PropTypes.func.isRequired,
      }

      const AddressForm = reduxForm({
        form: ADDRESS_FORM_NAME,
      })(Address)

      export default AddressForm

您会注意到有一个“value”的console.log。这会产生结果:

const onSuburbChange = (value) => { console.log('onSuburbChange: ', value) this.setState({ selectedSuburb: value }, () => { input.onChange(value) updatePostcodeValue(value ? value.postcode : null, sectionPrefix) }) }

我使用React-Select作为异步下拉列表。这一切都有效。如果我选择一个选项,我会得到下拉选项,但选择一个,它会给我错误。

我在这里使用的反应状态是selectSuburb选项,因为我不需要用这个来更新redux - 只是反应状态。

似乎没事,但我仍然得到错误。为什么我会收到此错误以及如何解决?

1 个答案:

答案 0 :(得分:1)

此特定错误是由<Address />组件是无状态功能组件且其中不能包含this.state对象或setState函数引起的。但是,更一般地说,您希望<AddressContainer />组件中的状态和函数可用于子<Address />组件,但这不可能发生。在这种情况下,您希望通过调用孩子的setState来修改父级的状态。

子React组件(在这种情况下为<Address />)只有其父级的状态/函数/属性,显式地作为道具传递给该组件。如果你想要更改必须在具有本地状态的组件上发生的组件的本地状态。如果你想让一个子组件触发父类的某种类型的函数调用,那么该函数必须作为prop传递给孩子,孩子可以调用它。

如果我正确理解您的代码,那么当Suburbs FormField按此顺序更改时,您希望发生3件事。

  1. selectedSuburb上的<AddressContainer />状态已更新。
  2. onChange中的Redux-Form <Field />的{​​{1}}被触发。
  3. <Address />行动被解雇。
  4. 如果这是正确的,那么您需要将updatePostCode移至onSuburbChange并将其作为道具传递给<AddressContainer />。但是,您无法在<Address />内拨打Redux-Form onChange的{​​{1}}。因此,您可以使该函数期望接收将在状态更新后触发的回调函数。然后,您可以在子组件中定义回调,但在父组件中定义状态更改函数。只要您传递父母所需的道具,例如<Field /><AddressContainer />,您就会变得金色。这就是它的样子:

    <强> AddressContainer

    updatePostCode

    <强>地址

    sectionPrefix

    正如您所看到的,export class AddressContainer extends Component { /* Everything else in this component */ onSuburbChange = (value, callback) => { this.setState({ selectedSuburb: value }, callback); } render() { /* Other stuff inside render */ return ( <Address initialValues={initialValues} addressData={addressData} handleSuburbSearch={this.handleSuburbSearch} onSuburbChange={this.onSuburbChange} updatePostcodeValue={this.props.updatePostcodeValue} sectionPrefix={sectionPrefix} /> ); } } 组件中的不同export const Address = (addressProps) => { return ( /* All other JSX */ <Field component={props => { const { input } = props; const handleSuburbChange = (value) => { addressProps.onSuburbChange(value, () => { input.onChange(value); addressProps.updatePostcodeValue(value ? value.postcode : null, addressProps.sectionPrefix) }); } return ( <Select.Async onChange={handleSuburbChange} /> ) }} ); } 变量之间会出现命名冲突,因此我调用主要道具props来避免这种情况。< / p>