Higher Order Component pattern causing confusion

The Context

Working on an old React codebase (started before the introduction of hooks in version 16), using GraphQL to mediate contracts between the frontend and the backend and Redux Form to control form states.
A colleague asked for some help after he noticed a bug caused by something seemingly un-related.

The Problem

He was working to add a new toggle field to a form. The form was submitted through a GraphQL mutation. The outer form component had some logic to control whether the submit button was disabled based on whether the formName was defined and whether the form was “pristine” (disabled={isPristine || !formName}).
The code looked like this:
const Form = () => { ... return ( <Form> <PageHeader button={<SubmitButton ... disabled={isPristine || !formName} />} /> </Form>) } const SubmitButton = ({ disabled }) => { ... return (<Button ... disabled={disabled} />) }
When he introduced a syntax error to the GQL mutation which was used to submit the form, unexpectedly, the submit button (which did not appear to be calling or invoking the GQL mutation) remained always disabled, regardless of the values of isPristine || !formName.


Whilst characterising the problem, we noticed that a GQL mutation was being sent on page load (the same one we would use to submit the form!).
The first thing we did to characterise the bug was adding a debugger; statement in the parent component and inside the button component to check the values of formName, isPristine, and disabled in the inner component.
Evaluating both in the console, in the outer component, isPristine || !formName evaluated to false, whereas the submit button was receiving the prop disabled as true!
My current mental model of what was happening:
notion image

The Extra Complexity

It turns out that there was an extra wrapper for the component which connects the submit button to redux and applies some custom middleware:
import { connect } from 'react-redux'; import { compose } from 'redux'; const SubmitButtonWrapper = compose( ..., // middleware withPermissionsValidation(), connect (null, mapDispatchToProps) )(SubmitButton;
My new mental model is:
notion image
The withPermissionsValidation function creates and returns a new class component, which has a componentDidMount method which calls the mutation. The idea is to invoke the mutation with no data, just to check if the user has permission to do the action.
The Higher Order Component (HOC) looked like this:
class WrappedComponent extends React.PureComponent { state = { // the child component always starts with disabled=true disabled: true, } componentDidMount() { this.props.mutation() // never gets hit if GQL syntax error in mutation .then(() => this.setState({ disabled: false })) .catch() } }
If there is a generic error which doesn’t relate to permissions (for example a syntax error in GQL), the mutation will return with an error and the disabled prop to the child component is overridden!