React-final-form: How can I access data from Form outside react-final-form

Created on 31 Jan 2019  路  12Comments  路  Source: final-form/react-final-form

So, I would like to acess the data from Form outside him, for example, I have multiple forms in only a page:

<Form
  validate={validate}
  onSubmit={ (values, props) => props.reset() }
  render={
  ({handleSubmit, invalid, ...props}) => {
    return(
      <form onSubmit={handleSubmit}>
      ...
      </form>
    )
  }}
</Form>

<Form
  validate={validate}
  onSubmit={ (values, props) => props.reset() }
  render={
  ({handleSubmit, invalid, ...props}) => {
    return(
      <form onSubmit={handleSubmit}>
      ...
      </form>
    )
  }}
</Form>

//And I want check if all forms are valid, for example:
<button disable={ form1.invalid || form2.invalid }></button>

Someone have a idea about that?

Most helpful comment

Nice, I did it:

export const myForm = ({subscription, ...props}) => {
    return(
        <Form
            onSubmit={(val)=> console.log(val)}
            subscription={subscription}
            render={({handleSubmit}) => (
                <form onSubmit={handleSubmit}>
                ....
                <FormSpy
                    subscription={{values: true, valid: true}}
                    onChange={(state) => {
                        const {values, valid} = state
                        props.exposeValues({values, valid})
                    }} />
                </form>
            )}
        />
    )
}

Work very well for me, thanks dude, cya

All 12 comments

I need to access form instance too to pass new values from api calls, disable fields calculate currencies on input event if i call from calculate it work one step latter.
Steps:
1. Have form with initial values
1.1 Need update values from api calls like calculate currencies
Optional First form can pass values that calls another form. Like starting form to declare second one
2 Pass submit values to api
2.1 Response values must rebind all form values and disable fields
3 Submit again just for confirm, form must stay same state
4 After success just horray, procces end

Thanks for response me, but I don't get it.
Can you explain with example?

I'm tried use final-form to don't need use redux(redux-form), but I'm thinking that will be more complex than I imagined

Create element(import from library) inside form

This is form render options render={({handleSubmit, form, submitting, pristine, values})

in function fnName you will access form functions and values this will work on every change, but then i change value in this function code start loop

Nice, I did it:

export const myForm = ({subscription, ...props}) => {
    return(
        <Form
            onSubmit={(val)=> console.log(val)}
            subscription={subscription}
            render={({handleSubmit}) => (
                <form onSubmit={handleSubmit}>
                ....
                <FormSpy
                    subscription={{values: true, valid: true}}
                    onChange={(state) => {
                        const {values, valid} = state
                        props.exposeValues({values, valid})
                    }} />
                </form>
            )}
        />
    )
}

Work very well for me, thanks dude, cya

I have a slightly different approach to this:

// in someForm.js
export const Form = ({setForm, onSubmit}) => (
  <FinalForm 
     onSubmit={onSubmit}
     render={({form, ...props}) => {
        setForm(form)
        return <form>...</form>
     }/>
) 

// in formMaster.js
const forms = {}
const setForm = formName => form => {forms[formName] = form}
const gatherFormData = async () => {
        let formData = {}
        for(let form of forms){
             formData = Object.assign(form, await form.submit())  // onSubmit must be promise
        }
        return formData
    }

const App = () => (
  <Form onSubmit={gatherData} setForm={setForm('someForm')}/> 
)

This allows you to reference the form from the master, submit everything at once. As a bonus, you can reset the forms using a basic forms.map(form => form.reset())

I needed more fine grained control (basically building a custom form generator using rff), so I had to go with ref and forwardRef.

// parent component / consumer of my library
class ParentComponent extends React.Component {
  constructor(props)
    //  ...
    this.formRef = React.createRef();
  }

  goToStep(step) {
    const { history } = this.props;
    const { form } = this.formRef.current;
    const { dirty } = form.getState();

    if (dirty) {
      form.reset();
    }
    history.replace(step);
  }

  render() {
    return (
      <MyFormGenerator
        ref={this.formRef}
        {...props}
      />
    )
  }
}


// my library component
class FormGenerator extends React.Component {
  // ...
  render() {
    return (
      <Form
        ref={this.props.forwardedRef}
        {...formProps}
        render={() => (
          // ...
        )}
      />
    )
  }
}

// wrap component in 'forwardRef` callback before exporting.
const FormGeneratorForwardRef = React.forwardRef((props, ref) => (<FormGenerator forwardedRef={ref} {...props} />));

export default FormGeneratorForwardRef;

In the Parent component we initialize a ref with createRef, and then in my library component we give it the ability to pass a ref to the form by wrapping the component with the forwardRef callback.

This gives me the entire form api available to the parent, and we are using it for all sorts of situations in addition to the example I provided here.

In your case, without that intermediate component (my library component), you can omit all of the forwardRef logic, and just create a ref and pass it to the Form component. That will do the trick.

In the case where you have multiple forms, it's just a matter of rinse and repeat.

I'll admit that its difficult to parse what is happening, but keep in mind that refs and forwardRef are supposed to be reserved for the rarest of use cases. In fact this is the first time I have had to do this in 3 years of React programming.

Nice, I did it:

export const myForm = ({subscription, ...props}) => {
  return(
      <Form
          onSubmit={(val)=> console.log(val)}
          subscription={subscription}
          render={({handleSubmit}) => (
              <form onSubmit={handleSubmit}>
              ....
              <FormSpy
                  subscription={{values: true, valid: true}}
                  onChange={(state) => {
                      const {values, valid} = state
                      props.exposeValues({values, valid})
                  }} />
              </form>
          )}
      />
  )
}

Work very well for me, thanks dude, cya

could you show me your code, please!, i don't know how to apply it. thanks

I need to check form data within a button click. I have a status field which contains the current workflow status. In one workflow step I need to read an array of data from the form and create an new record on another API endpoint.
How do I access form data as a structure?

I need to check form data within a button click. I have a status field which contains the current workflow status. In one workflow step I need to read an array of data from the form and create an new record on another API endpoint.
How do I access form data as a structure?

Can you show for us the code snippet? Do you can put on https://codesandbox.io/s/ ?

https://codesandbox.io/s/oq52p6v96y
in calculation you can access all data and do what you want on any field changes

https://codesandbox.io/s/32r824vxy1
_ onSubmit={onSubmit}
decorators={[calculator]}
render={({ handleSubmit, form, reset, submitting, pristine, values }) => (........_

_ component={YourComponentToApiCall}
form={form(from form props)}
values={values(from form props)}
...etc
/>_

in spy you can access all data and pass form props to custom component with life cycles. Where is and more options like bind form by id and get instance

Check this and take best solution

Hello guys!

@jweatherby In my case I use same solution with setForm(form), but it reduces performance. It lead to component rerender. Did you find some other ways to get form instance outside?

@jsrhodes15 Did your solution work in react-final-form v6 ? I can't get it work

// useConstant.js
import { useRef } from 'react';

export default function useConstant(init) {
  const initiated = useRef(false);
  const ref = useRef(undefined);

  if (!initiated.current) {
    initiated.current = true;
    ref.current = init();
  }

  return ref.current;
}
import { createForm } from 'final-form';

function MyComponent() {
  // `form` can also come from props, if needed
  const form = useConstant(() => createForm({
    initialValues,
    onSubmit
  }));

  // use your form...

  return (
    <Form
      form={form}
      render={() => {
        // You're <form />
      }}
    />
  );
}

https://github.com/final-form/react-final-form/blob/92cd23e237b43d1dd8e5598381ab58ae8f97f9ed/src/ReactFinalForm.js#L58-L74

Was this page helpful?
0 / 5 - 0 ratings