0

New to this.

I have looked for answers here and here.

am using Redux as well. As per good practice I have a container "AddressContainer" and its component "Address".

The AddressContainer is as follows -

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)

The Address component is as follows -

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

The problem is with the following function in the address component below and with setState which it says is undefined -

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

You will note there is a console.log for "value". This produces the result:

onSuburbChange: Object {id: 6810, suburbName: "Eaglehawk", postcode: "3556", state: "VIC"}

I am using React-Select as the async dropdown. This all works. If I select an option I get dropdown options but select one and it gives me the error.

I am using react state here for selectSuburb options as I dont need to update redux with this - just react state.

It seems all right but I still get the error. Why am I getting this error and how do I fix it?

2
  • You are setting state for a stateless functional component thats why it gives you this error Commented Jun 10, 2017 at 13:28
  • Could you elaborate. I am unsure as to how to rectify this. I thought I set state in the container specifically in the constructor for onSuburbChange. How do I fix this.. Commented Jun 10, 2017 at 14:47

1 Answer 1

1

This specific error is caused by the fact that the <Address /> component is a stateless functional component and cannot have a this.state object or a setState function in it. However, more generally it looks like you are expecting state and functions from the <AddressContainer /> component to be available to the child <Address /> component, but that cannot happen. In this case, you are wanting to modify the state of the parent by calling setState on the child.

A child React component (in this case <Address />) will only have state/functions/properties from its parent that are explicitly passed down as props to that component. If you want to change the local state of a component it must happen on the component with the local state. If you want to have a child component trigger some type of function call on the parent, then that function must be passed down as a prop to the child and the child can call it.

If I understand your code correctly, you want 3 things to happen when the Suburbs FormField is changed, in this order.

  1. The selectedSuburb state on <AddressContainer /> is updated.
  2. The onChange of the Redux-Form <Field /> in <Address /> is triggered.
  3. The updatePostCode action is fired off.

If that is correct, then you will need to move your onSuburbChange to the <AddressContainer /> and pass it to <Address /> as a prop. However, you cannot call the onChange of the Redux-Form <Field /> inside <AddressContainer />. Therefore, you can make that function expect to receive a callback function that will be fired off after the state updates. Then you can define the callback in the child component but the state-changing function in the parent. As long as you pass down needed props on the parent, such as updatePostCode and sectionPrefix, you'll be golden. Here's what it would look like:

AddressContainer

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}
      />
    );
  }
}

Address

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}
          />
        )
      }}
  );
}

As you can see, there is going to be a naming conflict between the different props variables in the <Address /> component, so I call the main props addressProps to avoid this.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.