rjsf-team / react-jsonschema-form

A React component for building Web forms from JSON Schema.
https://rjsf-team.github.io/react-jsonschema-form/
Apache License 2.0
14.14k stars 2.18k forks source link

Form.onChange not invoked for custom field types? #845

Closed johnament closed 2 years ago

johnament commented 6 years ago

Prerequisites

Description

First, I want to say that this is an awesome component. In the same time that I used to be able to spin up a JSF page that did CRUD, I could do as well with this component. Kudos to you guys!

I'm creating a simple wrapper for react-select to be used within a form. My component's pretty straight forward:

import Select from 'react-select' // and others ommitted
class SelectionField extends Component {
  static propTypes = {
    schema: PropTypes.object,
    id: PropTypes.string.isRequired
  }

  render () {
    return (
      <Select
        id={this.props.id}
        ref={(ref) => { this.select = ref }}
        onBlurResetsInput={false}
        onSelectResetsInput={false}
        options={this.props.schema.values}
        simpleValue
        clearable={this.props.schema.clearable}
        name={this.props.id}
        disabled={this.props.schema.disabled}
        value={this.props.schema.selectedValue}
        onChange={this.props.schema.onChange}
        rtl={this.props.schema.rtl}
        searchable={this.props.schema.searchable}
      />
    )
  }
}

In my form, I import this and mention it as a UI Widget:

uiSchema: {
        myField: {
          'ui:widget': SelectionField
        }
      }

I noticed at this point, my Form.onChange function isn't being called. This happens even when I don't pass in a onChange reference to the field. This is how my field is eventually defined:

schema: {
        title: 'Create Data',
        type: 'object',
        properties: {
          myField: {type: 'string', title: 'My Field', values: myFieldValues, selectedValue: defaultFieldValue, clearable: false, onChange: (e) => this.setField('myField', e)},
        }
      }

For completeness, this is what my onChange and setField functions look like, in addition to the final render

  onChange (e) {
    this.state.formData = e.formData
  }
  setField (field, value) {
    this.state.formData[field] = value
  }
  render () {
    return (
      <Form schema={this.state.schema} uiSchema={this.state.uiSchema} formData={this.state.formData} onChange={this.onChange}>
        <ButtonGroup>
          <LinkButton linkLocation='/landing-page/' className='pull-right' bsStyle='link' buttonLabel='Cancel' />
          <Button className='pull-right' bsStyle='primary' id='submit-button' onClick={() => this.save()}>Submit</Button>
        </ButtonGroup>
      </Form>
    )
  }

It could be that this is expected, however, because the Form's onChange doesn't get invoked, the underlying formData object doesn't get updated.

Steps to Reproduce

  1. Create custom field
  2. In UI set value
  3. See that the form's onChange is not invoked

Ideally, I'm providing a sample JSFiddle or a shared playground link demonstrating the issue.

Expected behavior

The form's onChange is invoked

Actual behavior

The field's onChange gets invoked (by the underlying component), but the form's onChange does not get invoked.

Version

1.0.1

miguelcast commented 6 years ago

@johnament In your example, in the custom field you used onChange from the schema object, but in the documentation show that onChange comes directly from the properties

import Select from 'react-select' // and others ommitted
class SelectionField extends Component {
  static propTypes = {
    schema: PropTypes.object,
    onChange: PropTypes.func,
    id: PropTypes.string.isRequired
  }

  render () {
    return (
      <Select
        id={this.props.id}
        ref={(ref) => { this.select = ref }}
        onBlurResetsInput={false}
        onSelectResetsInput={false}
        options={this.props.schema.values}
        simpleValue
        clearable={this.props.schema.clearable}
        name={this.props.id}
        disabled={this.props.schema.disabled}
        value={this.props.schema.selectedValue}
        onChange={this.props.onChange}
        rtl={this.props.schema.rtl}
        searchable={this.props.schema.searchable}
      />
    )
  }
}
johnament commented 6 years ago

@miguelcast thanks for the hint. I got something working. I did end up writing a local onChange function that sets the value and state (ensuring that the right state causes the component to keep track of the value). That onChange is delegating back to this.props.onChange which is the important step; since that causes the event to fire in the parent form.

Which documentation are you referring to?

miguelcast commented 6 years ago

This is of part documentation Custom widget components.