final-form / react-final-form

🏁 High performance subscription-based form state management for React
https://final-form.org/react
MIT License
7.35k stars 483 forks source link

Pass external variable to onSubmit #656

Open ignatevdev opened 4 years ago

ignatevdev commented 4 years ago

Are you submitting a bug report or a feature request?

Feature request

What is the current behavior?

onSubmit only receives predefined arguments without an option to pass some custom data through handleSubmit() or form.submit()

What is the expected behavior?

It would be very useful if handleSubmit could accept more than one variable, and pass these variables to the onSubmit call.

Use case

Consider having a form with two actions: Save and Send to moderation

In both cases the form should first go through basic validation, then save the data, but if user has chosen to "Send to moderation", we should make an additional validation check and require more fields, and if this validation passes - make one more api call.

It is currently possible to take the values from the form's render props and make a custom callback. However, the only non-hacky way to throw submit errors is to return them from onSubmit. Therefore, unless we are able to pass some custom data, in this case - user's action, we would not be able to achieve the desired behaviour without using some hacks like saving the action via the useRef before calling onSubmit.

SpadarShut commented 4 years ago

You can make the Send to moderation button type=button which will not submit the form, but call your custom function which will run submit and call whatever additional logic is needed.

rocketkittens commented 4 years ago

You can make the Send to moderation button type=button which will not submit the form, but call your custom function which will run submit and call whatever additional logic is needed.

Can you provide an example? I'm stuck on this also

alfondotnet commented 3 years ago

You can make the Send to moderation button type=button which will not submit the form, but call your custom function which will run submit and call whatever additional logic is needed.

Can you provide an example? I'm stuck on this also

I think Something like this should work

const sendToModeration = (values) => {}; 
const submitForm = (values) => {}; 

return <Form onSubmit={(values) => {
 // default submit code
submitForm(values);
}>
  {({ handleSubmit, form: { getState } }) => <form onSubmit={handleSubmit}>
  <input type="submit" />
  <input type="button" onClick={() => sendToModeration(getState().values}} />
</form>}
</Form>
shulcsm commented 3 years ago

Having a similar problem, there is no way to run server side validation and bind back errors. I figured out i can swap onSubmit but issue i'm having with that is that you can't get original onSubmit to restore it. From what i understand you can't do that from mutator either

() => {
  setConfig('onSubmit', async (values) => {
    console.log('submit', values);
  });
  submit().finally(
    () => {
      // setConfig('onSubmit', originalSubmit)
    }
  )
}
andrwo commented 3 years ago

I would also love to have this feature to pass a variable to onSubmit() via handleSubmit()

But seems like RFF is a little dead, which is sad because it is really awesome without all the hidden gotchas and slow performance I have seen in other state-form management libraries...

chaiwa-berian commented 3 years ago

Are you submitting a bug report or a feature request?

Feature request

What is the current behavior?

onSubmit only receives predefined arguments without an option to pass some custom data through handleSubmit() or form.submit()

What is the expected behavior?

It would be very useful if handleSubmit could accept more than one variable, and pass these variables to the onSubmit call.

Use case

Consider having a form with two actions: Save and Send to moderation

In both cases the form should first go through basic validation, then save the data, but if user has chosen to "Send to moderation", we should make an additional validation check and require more fields, and if this validation passes - make one more api call.

It is currently possible to take the values from the form's render props and make a custom callback. However, the only non-hacky way to throw submit errors is to return them from onSubmit. Therefore, unless we are able to pass some custom data, in this case - user's action, we would not be able to achieve the desired behaviour without using some hacks like saving the action via the useRef before calling onSubmit.

@ignatevdev This would be useful. I ran into the exact same challenge! I would like to pass an extra argument to onSubmit/handleSubmit then use it inside the submit handler to control what to do with the server response. How did you solve this with useRef?

BATCOH commented 3 years ago

Another approach is to use some field as container for your custom option:

const handleSubmit = async ({ action, ...values }, form) => {
  if (action === "delete") {
    ...
  } else {
    ...
  }
}
...
<Form onSubmit={handleSubmit}>
  {({
    form: {
      change,
    },
    submitting,
  }) => (
    <DeleteButton
      type="submit"
      onClick={() => change("action", "delete")}
      disabled={submitting}
    >
      Delete
    </DeleteButton>
    <Button
      type="submit"
      onClick={() => change("action", "save")}
      disabled={submitting}
      isLoading={submitting}
      label="Save"
    />
  )}
</Form>

Yep, its not about refs, but you can pass some flag and make condition inside submit based on its value.

chaiwa-berian commented 3 years ago

Another approach is to use some field as container for your custom option:

const handleSubmit = async ({ action, ...values }, form) => {
  if (action === "delete") {
    ...
  } else {
    ...
  }
}
...
<Form onSubmit={handleSubmit}>
  {({
    form: {
      change,
    },
    submitting,
  }) => (
    <DeleteButton
      type="submit"
      onClick={() => change("action", "delete")}
      disabled={submitting}
    >
      Delete
    </DeleteButton>
    <Button
      type="submit"
      onClick={() => change("action", "save")}
      disabled={submitting}
      isLoading={submitting}
      label="Save"
    />
  )}
</Form>

Yep, its not about refs, but you can pass some flag and make condition inside submit based on its value.

@batcoh, this looks good but what if I do not want to touch form state? Like a common no-op action but should take place after submit succeeds?

shulcsm commented 3 years ago

Is that solution even race-safe? Would change block and ensure state before submitting?

BATCOH commented 3 years ago

@shulcsm I think it safe enough, I use it in production. onSubmit fires only after onClick handler is done and only if click event is not prevented by event.preventDefault. FormAPI.change is synchronous. However, there is no guarantee, AFAIK events order is not described in the standards and depends on browser implementation. But it works well in my tests and with my use cases. I made a minimal example in Codesandbox, you can try it on your own.

BATCOH commented 3 years ago

@chaiwa-berian I personally use final-form-submit-listener to make something in separate handler after submit succeeded. And I still can check for "action" (or any other field) value in this handler via FormAPI.

metchel commented 2 years ago

Not sure if it would be recommended, but the onSubmit and other parts of the formApi can be configured dynamically using setConfig. setConfig doesn't seem to be listed in the documentation, but it's there in the code. I found this to work to implement different submission strategies:

const onSave = () => { ... }
const onSend = () => { ... }

const { setConfig, submit } = useForm();

const onClickSave = () => {
    setConfig('onSubmit', onSave);
    submit();
}

const onClickSend = () => {
    setConfig('onSubmit', onSend);
    submit();
}

return (
    <>
        <Button onClick={onClickSave}>Save</Button>
        <Button onClick={onClickSend}>Send</Button>
    </>
);

References: