andyhu92 / react-bootstrap4-form-validation

Simple React Components for form validation. Based on HTML5 Constraint validation API and Bootstrap4 style.
https://andyhu92.github.io/react-bootstrap4-form-validation/
MIT License
15 stars 4 forks source link

this.props.attachToForm is not a function & this.props.detachFromForm is not a function #1

Closed abakumov-v closed 6 years ago

abakumov-v commented 6 years ago

Hello!

When I'm trying use TextInput inside other component, I'm getting errors:

Uncaught TypeError: this.props.attachToForm is not a function
    at ProxyComponent.componentDidMount (index.js:130)
    at ProxyComponent.wrappedMethod (react-hot-loader.development.js:496)
    at commitLifeCycles (react-dom.development.js:9768)
    at commitAllLifeCycles (react-dom.development.js:11439)
    at HTMLUnknownElement.callCallback (react-dom.development.js:104)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:142)
    at invokeGuardedCallback (react-dom.development.js:191)
    at commitRoot (react-dom.development.js:11578)
    at completeRoot (react-dom.development.js:12475)
    at performWorkOnRoot (react-dom.development.js:12425)

Uncaught TypeError: this.props.detachFromForm is not a function
    at ProxyComponent.componentWillUnmount (index.js:135)
    at ProxyComponent.wrappedMethod (react-hot-loader.development.js:496)
    at callComponentWillUnmountWithTimer (react-dom.development.js:9687)
    at HTMLUnknownElement.callCallback (react-dom.development.js:104)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:142)
    at invokeGuardedCallback (react-dom.development.js:191)
    at safelyCallComponentWillUnmount (react-dom.development.js:9694)
    at commitUnmount (react-dom.development.js:9940)
    at commitNestedUnmounts (react-dom.development.js:9976)
    at unmountHostComponents (react-dom.development.js:10263)

The above error occurred in the <TextInput> component:
    in TextInput (created by OperationAmountInput)
    in div (created by OperationAmountInput)
    in OperationAmountInput (created by IncomeOperationItem)
    in div (created by Col)
    in Col (created by IncomeOperationItem)
    in div (created by FormGroup)
    in FormGroup (created by IncomeOperationItem)
    in form (created by ValidationForm)
    in ValidationForm (created by IncomeOperationItem)
    in li (created by IncomeOperationItem)
    in IncomeOperationItem (created by Connect(IncomeOperationItem))
    in Connect(IncomeOperationItem) (created by NewIncomeOperations)
    in div (created by NewIncomeOperations)
    in ul (created by NewIncomeOperations)
    in div (created by NewIncomeOperations)
    in div (created by NewIncomeOperations)
    in div (created by CardBody)
    in CardBody (created by LeftColumn)
    in div (created by Card)
    in Card (created by LeftColumn)
    in div (created by Col)
    in Col (created by LeftColumn)
    in div (created by Row)
    in Row (created by LeftColumn)
    in LeftColumn (created by NewIncomeOperations)
    in NewIncomeOperations (created by Connect(NewIncomeOperations))
    in Connect(NewIncomeOperations) (created by Route)
    in Route (created by Operations)
    in Switch (created by Operations)
    in div (created by TwoColumnsContainer)
    in TwoColumnsContainer (created by Operations)
    in Operations (created by Route)
    in Route (created by Routes)
    in Switch (created by Routes)
    in div (created by Routes)
    in Routes (created by App)
    in div (created by Col)
    in Col (created by App)
    in div (created by Row)
    in Row (created by App)
    in div (created by Container)
    in Container (created by App)
    in div (created by App)
    in div (created by App)
    in App
    in Router (created by ConnectedRouter)
    in ConnectedRouter
    in Provider

My sample is: 1) IncomeOperationItem.js - the parent of OperationAmountInput component, where I try using TextInput with validation logic:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
import { Row, Col, Form, FormGroup, Label, Input, FormText, Button, Card, CardBody } from 'reactstrap'
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import { ValidationForm, TextInput, SelectGroup, Checkbox, Radio } from "react-bootstrap4-form-validation"

import AddCopyOperationButton from 'components/Operations/OperationActions/AddCopyOperationButton';
import RemoveOperationButton from 'components/Operations/OperationActions/RemoveOperationButton';
import RemoveSelectedOperation from 'components/Operations/NewOperations/RemoveSelectedOperation';
import { IncomeOperationPropTypes } from 'constants/propTypes/operations';
import {
    changeIncomeOperationAmount, changeIncomeOperationPurse, changeIncomeOperationIncomeItem, changeIncomeOperationTags
} from 'actions/operations';
import {
    createNewTag
} from 'actions/handbooks';
import { PursePropTypes, IncomeItemPropTypes, TagPropTypes } from 'constants/propTypes/handbooks';
import TagsAutocompleteInput from 'components/Operations/NewOperations/base/TagsAutocompleteInput';
import * as arrayUtils from 'utils/arrayUtils';
import IncomeItemsSelector from 'components/Operations/NewOperations/base/IncomeItemsSelector';
import PursesSelector from 'components/Operations/NewOperations/base/PursesSelector';
import OperationAmountInput from 'components/Operations/NewOperations/base/OperationAmountInput';
import OperationTagsInput from 'components/Operations/NewOperations/base/OperationTagsInput';
import { findNewTags } from 'utils/tagsUtils';

class IncomeOperationItem extends Component {
    constructor(props) {
        super(props);
    }

    addCopyOfBlock = () => {
        const { incomeOperation, addCopyOfBlock } = this.props
        addCopyOfBlock(incomeOperation.number)
    }
    removeBlock = (number) => {
        const { removeBlock } = this.props
        removeBlock(number)
    }

    handleChangeAmount = (newAmount) => {
        const { changeIncomeOperationAmount, incomeOperation } = this.props
        changeIncomeOperationAmount(incomeOperation.number, newAmount)
    }
    handleChangeSelectedPurse = (selectedPurse) => {
        const { changeIncomeOperationPurse, incomeOperation } = this.props
        changeIncomeOperationPurse(incomeOperation.number, selectedPurse)
    }
    handleChangeSelectedIncomeItem = (selectedIncomeItem) => {
        const { changeIncomeOperationIncomeItem, incomeOperation } = this.props
        changeIncomeOperationIncomeItem(incomeOperation.number, selectedIncomeItem)
    }
    handleChangeSelectedTags = (selectedTags) => {
        const { tags, changeIncomeOperationTags, incomeOperation } = this.props
        changeIncomeOperationTags(incomeOperation.number, selectedTags)
        if (selectedTags) {
            findNewTags(selectedTags, tags, this.handleCreateNewTag)
        }
    }
    handleCreateNewTag = (newTags) => {
        console.log('New tags: ', newTags)
        const { createNewTag } = this.props

        if (newTags) {
            createNewTag(newTags)
        }
    }
    handleSubmitForm = (e, formData, inputs) => {
        e.preventDefault()

        console.log(e, formData, inputs)
    }

    render() {
        const { incomeOperation, isRemoveButtonEnabled, tags, incomeItems, purses } = this.props

        const removeButton = isRemoveButtonEnabled
            ? <RemoveSelectedOperation number={incomeOperation.number} onRemove={this.removeBlock} />
            : null

        const elementIdSuffix = incomeOperation.number
        const purseSelectId = `purseSelect${elementIdSuffix}`
        const plusInputId = `plus${elementIdSuffix}`
        const incomeItemSelectId = `incomeItemSelect${elementIdSuffix}`
        const tagsInputId = `tags${elementIdSuffix}`

        const isHighlightedForRemoveClass = incomeOperation.isHighlightedForRemove
            ? 'operation-details-item--for-remove'
            : ''

        return (
            <li className={`list-group-item list-group-item-light operation-details-item ${isHighlightedForRemoveClass}`}>
                <ValidationForm onSubmit={this.handleSubmitForm}>
                {/* <Form> */}
                    <FormGroup row>
                        <Col sm={5}>
                            <PursesSelector id={purseSelectId}
                                selectedPurse={incomeOperation.purse}
                                purses={purses}
                                onChange={this.handleChangeSelectedPurse} />
                        </Col>
                        <Col sm={6}>
                            <Label for={plusInputId} hidden>Укажите сумму операции</Label>
                            <OperationAmountInput id={plusInputId}
                                selectedValue={incomeOperation.plus}
                                onChange={this.handleChangeAmount}
                            />
                            {/* If I'm uncomment this lines, the error is not occurs */}
                            {/* <TextInput name='text' id={plusInputId} placeholder='Сумма операции'
                                value={incomeOperation.plus}
                                onChange={this.handleChangeAmount}
                                required                                
                            /> */}
                        </Col>
                        <Col sm={1} className='text-right'>
                            <AddCopyOperationButton number={incomeOperation.number} onClick={this.addCopyOfBlock} />
                        </Col>
                    </FormGroup>
                    <FormGroup row>
                        <Col sm={5}>
                            <IncomeItemsSelector id={incomeItemSelectId}
                                selectedIncomeItem={incomeOperation.incomeItem}
                                incomeItems={incomeItems}
                                onChange={this.handleChangeSelectedIncomeItem}
                            />
                        </Col>
                        <Col sm={6}>
                            <OperationTagsInput id={tagsInputId}
                                selectedTags={incomeOperation.tags}
                                tags={tags}
                                onChange={this.handleChangeSelectedTags}
                                onCreateNewTag={this.handleCreateNewTag}
                            />
                        </Col>
                        <Col sm={1} className='text-right'>
                            {removeButton}
                        </Col>
                    </FormGroup>
                {/* </Form> */}
                </ValidationForm>
            </li>
        );
    }
}

IncomeOperationItem.propTypes = {
    incomeOperation: IncomeOperationPropTypes,
    isRemoveButtonEnabled: PropTypes.bool,
    tags: PropTypes.arrayOf(TagPropTypes),
    purses: PropTypes.arrayOf(PursePropTypes),
    incomeItems: PropTypes.arrayOf(IncomeItemPropTypes),

    addCopyOfBlock: PropTypes.func,
    removeBlock: PropTypes.func,
};

export default connect(
    null,
    {
        changeIncomeOperationAmount, changeIncomeOperationPurse, changeIncomeOperationIncomeItem, changeIncomeOperationTags,
        createNewTag
    }
)(IncomeOperationItem);

2) The OperationAmountInput component is:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Label, Input } from 'reactstrap'
import { ValidationForm, TextInput, SelectGroup, Checkbox, Radio, BaseFormControl } from 'react-bootstrap4-form-validation'
import { max20DigitsWithPrecision2Regex } from 'constants/regex'

const operationAmountValueErrorMessage = 'Проверьте, корректно ли указана сумма дохода - правильно: "1200", "500.15", "430,20" (после запятой/точки - максимум 2 символа)'
const operationAmountRequiredErrorMessage = 'Укажите сумму операции'

class OperationAmountInput extends Component {
    constructor(props) {
        super(props);
    }

    handleChangeOperationAmount = (e) => {
        const { onChange } = this.props
        if (onChange)
            onChange(e.target.value)
    }

    render() {
        const { id, selectedValue } = this.props
        const title = `${operationAmountRequiredErrorMessage}. После ввода - ${operationAmountValueErrorMessage.toLowerCase()}`
        return (
            <div>
                <Label for={id} hidden>Укажите сумму операции</Label>
                <TextInput type='text' name='text' id={id} placeholder='Сумма операции'
                    value={selectedValue}
                    onChange={this.handleChangeOperationAmount}
                    required
                    pattern={max20DigitsWithPrecision2Regex}
                    errorMessage={{
                        required: operationAmountRequiredErrorMessage,
                        pattern: operationAmountValueErrorMessage,
                    }}
                />
            </div>
        );
    }
}

OperationAmountInput.propTypes = {
    id: PropTypes.string,
    selectedValue: PropTypes.string,

    onChange: PropTypes.func,
};

export default OperationAmountInput;

3) package.json is:

{
  "name": "finance_client_web_spa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "webpack-dev-server --hot --inline",
    "build": "webpack"
  },
  "author": "Abakumov Valeriy",
  "devDependencies": {
    "autoprefixer": "^7.1.6",
    "babel-core": "^6.13.2",
    "babel-loader": "^6.2.4",
    "babel-preset-env": "^1.6.0",
    "babel-preset-react": "^6.11.1",
    "babel-preset-react-hmre": "^1.1.1",
    "babel-preset-stage-0": "^6.5.0",
    "css-hot-loader": "^1.3.9",
    "css-loader": "^0.28.9",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.8.3",
    "postcss-loader": "^2.1.3",
    "precss": "^3.1.2",
    "prop-types": "^15.6.1",
    "react-hot-loader": "^4.0.1",
    "redux-logger": "^3.0.6",
    "sass-loader": "^6.0.7",
    "style-loader": "^0.19.1",
    "url-loader": "^1.0.1",
    "webpack": "^3.0.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.2"
  },
  "dependencies": {
    "@fortawesome/fontawesome": "^1.1.5",
    "@fortawesome/fontawesome-free-brands": "^5.0.10",
    "@fortawesome/fontawesome-free-regular": "^5.0.10",
    "@fortawesome/fontawesome-free-solid": "^5.0.10",
    "@fortawesome/fontawesome-free-webfonts": "^1.0.6",
    "availity-reactstrap-validation": "^2.0.2",
    "bootstrap": "^4.0.0",
    "font-awesome": "^4.7.0",
    "history": "^4.7.2",
    "immutability-helper": "^2.6.6",
    "immutable": "^4.0.0-rc.9",
    "jquery": "^3.3.1",
    "keymirror": "^0.1.1",
    "loaders.css": "^0.1.2",
    "popper.js": "^1.14.3",
    "prop-types": "^15.6.0",
    "react": "^16.0.0",
    "react-addons-css-transition-group": "^15.6.2",
    "react-autosuggest": "^9.3.4",
    "react-bootstrap4-form-validation": "^1.0.5",
    "react-css-transition": "^0.7.4",
    "react-day-picker": "^7.0.7",
    "react-dom": "^16.0.0",
    "react-loader-advanced": "^1.7.1",
    "react-loaders": "^3.0.1",
    "react-overlay-loading": "^1.0.3",
    "react-redux": "^5.0.6",
    "react-router-dom": "^4.2.2",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-select": "^1.2.1",
    "react-tagsinput": "^3.19.0",
    "react-transition-group": "^2.4.0",
    "reactstrap": "^5.0.0",
    "redux": "^3.7.2",
    "redux-thunk": "^2.2.0",
    "reselect": "^3.0.1"
  }
}

What I'm doing wrong?

andyhu92 commented 6 years ago

Thank you for reporting the issue! I checked you code and found that you are using TextInput without wrapped with a direct ValidationForm inside your OperationAmountInput. Even if you already have a ValidationForm in your IncomeOperationItem, the TextInput inside OperationAmountInput was not registered properly. Currently the workaround is to add another ValidationForm component in your OperationAmountInput, and at the meantime I'll try to fix this issue ASAP.

andyhu92 commented 6 years ago

I just updated the code, could you try to install the latest version? It should resolve the issue.

abakumov-v commented 6 years ago

@andyhu92 cool, thanks! Unfortunately, I can only get there on the weekend.

andyhu92 commented 6 years ago

No worry, just let me know if that's working for you when you are available

abakumov-v commented 6 years ago

Yeah, it's work, thanks!