2

I have a React Component that calls a Query that expects to receive an array of objects (for validation data). I then use it to validate an html form, that is in turn defined inside of an apollo Mutation element. The way it is structured within the return statement of the component render() method works but it looks cumbersome and it reminds me of the callback hell era. What I really want to do is to get rid of the <QueryCustomerValidations> element in the render() method and move it into (ideally) the componentDidMount lifecycle event. So that the validators can be loaded there for later use within the form, then leave the <MutationCreateCustomer>inside render().

Right now, I have to wrap the form inside the mutation. The mutation inside the arrow function that the query returns in order to receive the async data in the proper order.

    //-------------------------------------------------------------------------
    // Component Lifecycle Eventhandler Methods
    //-------------------------------------------------------------------------
    componentDidMount()
    {

    }    


    //-------------------------------------------------------------------------
    // Render Method Section
    //-------------------------------------------------------------------------
    public render(): JSX.Element
    {
        // Return Form
        return (
            <React.Fragment>
                {/* PAGE TITLE  */}
                <h2 className="text-center mb-3">Agregar Nuevo Cliente</h2>

                {/* LOAD VALIDATIONS INTO STATE  */}
                <QueryCustomerValidations
                    query={Q_GET_CUSTOMER_VALIDATIONS}
                >
                    {({ loading: loadingValidations, error: errorValidations, data: dataValidations }) =>
                    {
                        if (loadingValidations)
                        {
                            return "Cargando..."
                        }
                        if (errorValidations)
                        {
                            return `Error: ${errorValidations.message}`
                        }
                        if (dataValidations)
                        {
                            const validators: ValidationDescriptor[] = []
                            dataValidations.getCustomerValidations.forEach((validator) => {
                                validators.push(validator as ValidationDescriptor)
                            })
                            this.validators.setValidators(validators)
                        }

                        /* DATA ENTRY FORM  */
                        return (
                            <div className="row justify-content-center">

                                <MutationCreateCustomer 
                                    mutation={M_CREATE_CUSTOMER}
                                    onCompleted={() => this.props.history.push('/')}
                                >
                                    {(createCustomer: any) => {
                                        return (                                            
                                            <form name="frmNewCustomer"
                                                    className="col-md-8 m-3"
                                                    onSubmit={e => this.frmNewCustomer_submit(e, createCustomer)}
                                            >

                                                { this.ctrl_form_layout(this.props, this.state, this.validators) }

                                            </form>
                                        )
                                    }}
                                </MutationCreateCustomer>
                            </div>
                        )
                    }}

                </QueryCustomerValidations>

            </React.Fragment>
        );
    }

Here for documentation purposes are the interfaces to create the Query. Since I get some of this data from the server using the apollo client, a simple graphql query solution on the onDidMount() would not work in this case.

getCustomerValidations.ts (Interfaces)


// ====================================================
// GraphQL query operation: getCustomerValidations
// ====================================================

export interface getCustomerValidations_getCustomerValidations {
  __typename: "ValidationDescriptor";
  field: string | null;
  type: string | null;
  required: boolean;
  max: number | null;
  min: number | null;
  regex: string | null;
}

export interface getCustomerValidations {
  getCustomerValidations: getCustomerValidations_getCustomerValidations[];
}

customer-validations.query.ts (Client side query types)


//---------------------------------------------------------------------------------
// Imports Section (React/Apollo Libs)
//---------------------------------------------------------------------------------
import { gql }                              from 'apollo-boost';
import { Query }                            from 'react-apollo'

import { getCustomerValidations }            from '../../typeDefs/operations/getCustomerValidations'

//---------------------------------------------------------------------------------
// GQL Query: Customers
//---------------------------------------------------------------------------------
export const Q_GET_CUSTOMER_VALIDATIONS = gql`
    query getCustomerValidations {
        getCustomerValidations
        {
            field
            type
            required
            max
            min
            regex
        }
    }
`;

//---------------------------------------------------------------------------------
// Query Class: Customers
//---------------------------------------------------------------------------------
export class QueryCustomerValidations extends Query<getCustomerValidations> { }


The right solution can be a way to copy -and trigger- the <QueryCustomerValidations>element from the componentDidMount() method or, how to take the element away from the render() method and call it like with some sort of "await" way of doing it so it can be called first and the mutation after (and using the data from the query).

Thanks, I know this one is not really easy to figure out.

Geo
  • 2,321
  • 2
  • 12
  • 19
Will de la Vega
  • 536
  • 1
  • 5
  • 17

1 Answers1

2

You're looking for 'old' (used before <Query/> component) HOC pattern (with compose) described here, here and here.

Composing "graphql(gqlquery ..." requested at start (without conditional skip option) with one or more (named) "gqlmutation ..." (called on demand) gives you a clear, readable solution.

compose(
  graphql(Q_GET_CUSTOMER_VALIDATIONS),
  graphql(gql`mutation { ... }`, { name: 'createSth' })
)(SomeComponent)

will pass data and createSth props to <SomeComponent/> then this.props.data object will contain loading, error and getCustomerValidations fields/properties. It's described here.

Query will be called at start (you can expect true in this.props.data.loading), no need to run query at cDM(). Mutation can be run using this.props.createSth() - custom name defined above (instead default prop name mutate).

Of course you can mix them with other required HOC's, f.e. redux connect(), withFormik() etc. - simply by adding single line of code.

xadm
  • 8,219
  • 3
  • 14
  • 25
  • Is it possible to use the graphql(gql query ..." solution to load the validators in componentDidMount(), so that the array can be stored and be available in the render() method? If so, please, could you kindly provide an example of how to do that? – Will de la Vega May 27 '19 at 19:08
  • Thanks, I realize the HOC pattern may not be the newest thing and also personally I don't like much this solution, however since at this time there doesn't seem to be a better way to do this, then this would be the best solution. maybe one day there will be something like `````` That way the code would look way cleaner and we wouldn't need to resource to cumbersome patterns and structures. – Will de la Vega May 27 '19 at 21:11
  • Mix it? You can use emhance component with one HOC for query (without compose) and use `` component in `render` if you like. Compose pattern is usable with more complex scenarios, full component lifecycles, additional internal states, event handlers etc. – xadm May 27 '19 at 21:24
  • 1
    If you only need a `shortcut` then overwrite `render` in 'QueryCustomerValidations/>` - render conditionally loading (and error) and render child when data ready. This way you can simply wrap directly your `` in one-liner. – xadm May 27 '19 at 21:39