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.3k stars 2.19k forks source link

Is there a way to update outer formData from within a custom widget ? #1181

Closed KKS1 closed 5 years ago

KKS1 commented 5 years ago

Prerequisites

Description

Right now, a widget can update only the field it's associated with, using prop.onChange. I have a case, where one of the widgets is actually a google autocomplete address. This, requires emit or modify the formData, so that other fields such as city, state, zip can also be updated in formData; and thus, to make the widgets associated with them pick the relevant formData update, and fill in the value.

Version

Latest ^1.2.0

christianeide commented 5 years ago

I have made a solution for something similar as this. I have field that fetches data from an API and populates other fields with that data. As far as I know there is not a readymade solution for this.

Simplified code below

On my form I defined a custom formContext <Form formContext={{ updateOtherForms: this.updateOtherForms }} >

This formContext has a simple function that updates formData (I dont use local state for formData, so you have to make this one out a little bit your self)

updateOtherForms = (newContent) => {
    this.props.updatePartOfStateObject('formData', newContent)
}

Then in my ui:schema for the requested parameter I have added a custom property that maps data from the incomming API-request to other fields. By doing it this way it is simple to reuse the API-fetcher without having to modify my widget.

'url': {
    'ui:field': 'fetchAPI',
    'ui:options': {
      'populate': {
        'headline': 'title',
        'text': 'lead',
        'image.url': 'image.crops.16:9.80'
      }
    }
  },

Lastly my widget runs this function when the user clicks a button to fetch data (I have also added options to use queryparameters on the populate-data as well to modify incoming data, but I have left that part out here to simplify)

handleClick = (e) => {
    const fields = {...this.props.uiSchema['ui:options'].populate}
    const content = []

    Object.keys(fields).forEach(function (key) {
        content.push({ ['content.' + key]: fields[key] })
    })

    this.props.formContext.updateOtherForms(content)
}

Hope this helps!

KKS1 commented 5 years ago

Thanks @christianeide . Appreciate the help. The way I finally resolved this, is by creating a custom Address field, which comprises of all the widgets city, state, zip etc. The Address field, then, takes object type and updates/ persists the formData properties, to populate all the underlying widgets its rendering.

epicfaace commented 5 years ago

Closing this issue, but feel free to repoen it if you have any additional questions!

OleksiL commented 3 years ago

Thanks @christianeide . Appreciate the help. The way I finally resolved this, is by creating a custom Address field, which comprises of all the widgets city, state, zip etc. The Address field, then, takes object type and updates/ persists the formData properties, to populate all the underlying widgets its rendering.

Hi. Could you, please, provide more details about your solution? I need to solve a similar task - one widget populates many other fields (like you select a user and its email, name, age are being populated in other fields. all the data is retrieved by the 'main' widget). I understood christianeide's solution: he adds a function which updates formData to formContext and this formContext object is passed to every widget. So, when widget fires onChange it can also call that function and modify formData on global level. Your solution seems more correct to me, but I don't understand how it works:

  1. How do you I specify that for certain schema field a custom field should be used?
  2. Does every widget in custom field corresponds to some field in schema?
  3. Is a type of 'Address' - an object consisting of other fields? How does it look in form data? like this: formData: { Adress: { city: city1, zip: zip }, SomethingElse: somethingelse1 }
KKS1 commented 3 years ago

Hi @OleksiL , here is a glimpse:

  1. Use uiSchema

    uiSchema = {
      ......,
      'addressFieldName': {
         'ui:field': 'address'
      },      
    ....
    }

    where we have also, provided our RJSF Form, a set of custom fields and widgets that you want to register. E.g. in this case (simplified version)

    const customFields = {
       ....,
       address: CustomAddress, // (this is your custom Address Field component, which will be registered within Form as `address` field),
       ...
    }

    Then,

    <Form 
    fields={customFields}
    .....
    >
  2. For schema now, this will look like

    schema = {
    ...,
    addressFieldName: {
    type: 'object',
    title: ' ',
    properties: {
     city: {
        type: 'string'
    },
    zip: {
        type: 'string'
    } 
    },
    ....
    }
  3. formData will then contain an object addressFieldName which will then have city and zip strings underneath.

Hope it helps, cheers!

-- kks1

beatrizpatriota commented 1 year ago

I have made a solution for something similar as this. I have field that fetches data from an API and populates other fields with that data. As far as I know there is not a readymade solution for this.

Simplified code below

On my form I defined a custom formContext <Form formContext={{ updateOtherForms: this.updateOtherForms }} >

This formContext has a simple function that updates formData (I dont use local state for formData, so you have to make this one out a little bit your self)

updateOtherForms = (newContent) => {
    this.props.updatePartOfStateObject('formData', newContent)
}

Then in my ui:schema for the requested parameter I have added a custom property that maps data from the incomming API-request to other fields. By doing it this way it is simple to reuse the API-fetcher without having to modify my widget.

'url': {
    'ui:field': 'fetchAPI',
    'ui:options': {
      'populate': {
        'headline': 'title',
        'text': 'lead',
        'image.url': 'image.crops.16:9.80'
      }
    }
  },

Lastly my widget runs this function when the user clicks a button to fetch data (I have also added options to use queryparameters on the populate-data as well to modify incoming data, but I have left that part out here to simplify)

handleClick = (e) => {
    const fields = {...this.props.uiSchema['ui:options'].populate}
    const content = []

    Object.keys(fields).forEach(function (key) {
        content.push({ ['content.' + key]: fields[key] })
    })

    this.props.formContext.updateOtherForms(content)
}

Hope this helps!

Hi @christianeide could you please share your code in a codepen? It would really help me, I have a similar task updating other fields with a zip code