mardi 2 mai 2023

Abstractions in React - HOC and hooks, possible or am I just hitting the limitations of React?

Say I have a create (POST) / update (PUT) endpoint for 10 (and growing) different resources.

All 10 of these resources implement the same popup form for create and update, design and flow wise.

Writing a CreateFirstForm UpdateFirstForm CreateSecondForm UpdateSecondForm is becoming super tedious. It's basically copy-pasta except the endpoint, fields and labels in the UI. They also all have "useCreateResource" and "useUpdateResource" hooks incorporated that are basically calls to react-query's useQuery (calls fetch and manages state for the incoming / outgoing data)

So, I think, let's write a higher order component, that's what they are for, to abstract away common logic.

I initially thought I could parse the id in the URL to discern whether or not the call is a POST vs PUT.

It was all going well, code-wise, until I hit the dreaded Hook is called conditionally issue. The hook in question is basically id ? useGetResource(id) : { } which determines the initial data a form should edit (existing vs blank).

Yes I understand I could separate this "upsert" flow into Create and Update flows starting with CreateForm and UpdateForm thereby bypassing all conditional hooks... but then it completely defeats my intention of abstracting away redundant code and I'm left with where I started at, 20 different form components for 10 different resources.

I realize this might be easier to understand with examples so here's some pseudocode with the interface of how I would like it to behave with one resource, a recipe.

// hooks
useGetRecipe = () => {}
useCreateRecipe = () => {}
useUpdateRecipe = () => {}
useUpsertRecipe = (id?) => {
  const recipe = id ? useGetRecipe(id) : {};
  const submit = // do the create vs update
  return {
    recipe,
    submit,
    ...
  }
}



// Components

// the Higher order component, takes in the form, the hook and edits the recipe in place. Submission will call submit with the updated data

const upsertResource = (FormComponent) => (hook) => () => {
   const id = useUrlParams('id') //presence or not should determine POST vs PUT
   const { recipe, submit } = hook(id);
   
   return <FormWrapper data={recipe} submit={submit}><FormComponent/></FormWrapper>
} 

//edits a recipe, mapping name attr to property in recipe

const RecipeForm = () => {
  return <Field name="name" />
}

// The top level component, which basically calls the HOC with the hook. All it would take for every resource if conditional hooks were allowed!

const UpsertRecipe = () => {
  const Upsert = upsertResource(RecipeForm)(useUpsertRecipe)
  return <Upsert><RecipeForm/></Upsert>
}

So using the above I should be able to keep 10 resources into 10 forms in which simply having "id" or not could determine a create vs update, but alas, hooks cannot be called conditionally.

Now, I started breaking up the "upsert" into create vs update.... but I just realized I'm back where I started, having 2 forms for each resource thereby making my HOC / custom hook pattern useless, which is fine, but I was wondering if there was a better way to do stuff like this or should I just toss it up to, "it's just UI... sometimes we write repeated code."

Thanks!

Aucun commentaire:

Enregistrer un commentaire