AxeWP / wp-graphql-gravity-forms

GraphQL API for interacting with Gravity Forms.
GNU General Public License v3.0
164 stars 29 forks source link

Change in Mutation #4

Closed rotexhawk closed 4 years ago

rotexhawk commented 4 years ago

First thank you for creating this plugin. I wanted to see what's the reason behind the API change. The previous implementation was much simpler and intuitive. It doesn't make sense for a user to send a draft mutation and then send mutations for updating every field and then send a final mutation to submit the form. Unless I am not understanding the new API, this is overly complicated. I know it might make sense to keep the implementation similar to the gravity form's core API but to the end users it doesn't matter much. It might be best to add a stand-alone mutation to submit the form in one request.

This makes sense

const [submit] = useMutation(formMutation);

const handleSubmit = async (values, { setSubmitting, setErrors }) => {
        const allTextValues = convertToEntries(form, values);
        try {
            const data = await submit({
                variables: {
                    input: { allTextValues, clientMutationId, formId },
                },
            });
        } catch (e) {
            console.error(e);
            setErrors({ message: e.message });
        } finally {
            setSubmitting(false);
        }
    };
kellenmace commented 4 years ago

Hey @rotexhawk - thanks for the feedback.

In short: I'd love to be able to have a simple, single mutation for doing form submissions, but because Gravity Forms forms can have any number of fields in any order, and the GraphQL specification does not current support input union types, it's not currently possible without hacky workarounds. Here's some more info:

I originally had a very simple mutation for submitting forms, similar to the mock one in the code snippet you provided. The problem with it is that it assumes that every form field value can be represented as a single string of text. There are many that can't, however. Examples: Address fields have an array of 6 values, Chained Select fields have an array of X number of values, Multiselect fields have an array of X number of values, Name fields have an array of 5 values, File Upload fields get submitted as multipart form data, etc.

When using GraphQL, you have to define your schema so that GraphQL "knows" exactly which input fields a mutation has. This poses a problem for form builder tools like Gravity Forms -- how do you define the input fields in your schema if the Gravity Forms forms on the site can have any fields, in any order? I worked closely with @jasonbahl, the creator of WPGraphQL on this problem, and these were the solutions we came up with:

  1. Use an input union type, telling GraphQL that the mutation will be passed an array of field values, then define a resolver function for determining which field value type each one in the array is. This would be the ideal solution and would allow us to have a single mutation for submitting forms. Unfortunately though, input union types are currently not part of the GraphQL specification. You can read more about that in this RFC: https://github.com/graphql/graphql-spec/blob/master/rfcs/InputUnion.md

  2. At a hook early in the WordPress lifecycle, run a query to get all Gravity Forms and the fields within. Use that data to dynamically build out the GraphQL schema, defining a separate submission mutation for each form. So you'd end up with mutations like submitGravityForm1, submitGravityForms2, etc., each with their own set of input types. This approach is not ideal because:

    • It adds lots of additional queries to each page load (even if transients or an object cache were used to try to mitigate those extra DB hits).
    • There would be no single-source-of-truth mutation for submitting forms - just a wild west scenario where every WP site that uses this plugin would have vastly different form submission mutations for each form, each with their own names and fields. This would result it a lot of inconsistencies, confusion, the potential for bugs, etc.
  3. Use a series of mutations: one to create a new draft entry, one or more mutations to update that draft entry with the form values to be saved, then one final mutation to submit the draft, turning it into a permanent entry. This was chosen as the best solution, since it results in a few consistent mutations that always work the same across all WP sites and all Gravity Forms. It also provides the flexibility needed to support any form with any fields in any order.

So that's why #3 was chosen as the best solution currently available. I completely agree that it would be more ideal to have a single mutation for handling form submissions, but as I stated above, until the GraphQL spec support input union types, that's not a viable option for us. Please let me know if you have any more feedback on the approach - I'd love to hear other ideas.

Notes on multiple HTTP requests and resuming draft entries

If you're concerned having to fire off multiple HTTP requests rather than one, please note that you don't even have to wait until the user hits "submit" to execute them. For example, in my decoupled frontend React app, I'm doing this:

This has the added benefit of saving the user's progress in real time. If the user were to close their browser and open your app back up later, you would be able to grab the resume token from their previous session and pass it in to the gravityFormsEntry() query to get the draft entry data, then use it to populate the form inputs so they could pick right back up where they left off. The ability to resume forms like that is also a feature we're planning to implement in the React frontend codebase I work on.

Defining your own form-specific submission mutation

If you don't want to use the form submission mutations that the plugin provides, you can totally roll your own that's specific to one form. So if you have a single contact form on your site, for example, you can register a new mutation that has the input fields it needs hardcoded, and then ultimately call Gravity Forms' GFAPI::add_entry() method in its resolver to submit the entry. If you go with this approach, I would just make sure you know the GF form fields in the WP backend are not going to change, or else the form fields in your backend and frontend could get out-of-sync with one another. More details on defining your own mutations can be found here: https://docs.wpgraphql.com/extending/mutations/.

rotexhawk commented 4 years ago

@kellenmace Really appreciate the detailed explanation. Learned a lot reading through the RFC. The process you outlined for the form submission makes it easier to reason about the FE implementation. I will try to implement this and get back to you if I run into any issues. Thanks again!

kellenmace commented 4 years ago

@rotexhawk Yeah, my pleasure! Cool - sounds great. 👍🏼

justlevine commented 3 years ago

Update: as of v0.4.0 there is a new submitGravityFormsForm mutation, that lets you pass all the form field values at once.

It's still not the ideal situation (some of the more complex fields require a field-specific value property). but it should offer a markedly improved DX until Input Unions are part of the GraphQL spec.

earwickerh commented 11 months ago

Hi, is the workaround still necessary since the introduction of "OneOf Input Objects" variant to the GraphQL spec https://github.com/graphql/graphql-spec/pull/825? Thanks, and great work!

justlevine commented 11 months ago

Hi, is the workaround still necessary since the introduction of "OneOf Input Objects" variant to the GraphQL spec graphql/graphql-spec#825? Thanks, and great work!

Hey @earwickerh

As you can see from the PR your linked to, oneOf has yet to be merged into the spec. More importantly, it's yet to be added to PHP graphql-php.