Home > Mobile >  Refactoring a React component for reuse
Refactoring a React component for reuse

Time:12-26

I'm looking for some advice in refactoring the following React component:

const Block = () => {
    const {blockId} = useParams();
    const {register, control, handleSubmit} = useForm();

    const isNewBlock = typeof blockId === 'undefined';

    const [saveBlockMutation] = useSaveBlockMutation();
    const [deleteBlockMutation] = useDeleteBlockMutation();
    const {data, loading, error} = useGetBlockQuery({
        skip: isNewBlock,
        variables: {block_id: parseInt(blockId!)}
    });

    const saveBlock = (input: any /* todo: type it */) => {
        saveBlockMutation({variables: {input: input}})
            .then(result => {
                if (result.data?.saveBlock) {
                    // show notification
                }
            })
    };

    const deleteBlock = (blockId: number) => {
        deleteBlockMutation({variables: {block_id: blockId}})
            .then(result => {
                if (result.data?.deleteBlock) {
                    // show notification
                }
            })
    }

    return (
        <LoaderHandler loading={loading} error={error}>
            {!loading && (
                <>
                    <Header text={data ? "Block: "   data.block.identifier : "Add Block"}>
                        <Button onClick={handleSubmit(saveBlock)}>Save</Button>
                        {!isNewBlock && (<Button onClick={() => deleteBlock(parseInt(blockId!))}>Delete</Button>)}
                    </Header>
                    <Form data={data} register={register} control={control}/>
                </>
            )}
        </LoaderHandler>
    )
}

This currently works fine, but I'll be adding a number of other components that should behave the exact same way:

  • get some ID from the URL
  • load some data
  • render a form
  • save mutation
  • delete mutation
  • save/delete buttons

I feel that everything in that list I should be able to extract into something more generic, except for the "render a form" part.

I'm having trouble determining what that "something" is. Maybe a HOC is suitable here? I would end up with something like:

const Block = (props: WithCrudProps) => {
    // we only render a form here
}

export default withCRUD(
    Block, 
    {
        deleteMutation: DeleteBlockMutation,
        saveMutation: SaveBlockMutation,
        getQuery: GetBlockQuery,
        // etc.
    }
);

But that feels like it could get messy real fast. What is the "react way" to approach this?

CodePudding user response:

I think it would be hard to have a good implementation for withCRUD, because of all relations here:

// you need param name to extract it from params:
const params = useParams();
const param = params[paramName];
// then you need to convert param to query variables:
const queryVariables = makeQueryVariables(param)
// and you will need more of that for mutations

So I would recommend custom hook

interface UseCrudParams<T, Vars> {
  id?: number;
  initialData?: T;
  onSave: (vars: Vars) => Promise<void>;
  onDelete: () => Promise<void>
}

function useCrud<T, Vars>({
  id,
  initalData,
  onSave,
  onDelete,
}: UseCrudParams<T, Vars>): CrudProps { /* ... */}

// and then when you use it you adapt mutations to form
const formProps = useCrud({
  id: blockId,
  initialData: data,
  onSave: variables => saveBlockMutation({ variables  }),
  onDelete: () => deleteBlockMutation({ variables: { block_id: blockId } }),
})

And create UI component for form layout:

function FormLayout({ header, loading, error, onSave, showDelete, onDelete, children }: FormLayoutProps) {
  return (
        <LoaderHandler loading={loading} error={error}>
            {!loading && (
                <>
                    <Header text={header}>
                        <Button onClick={onSave}>Save</Button>
                        {showDelete && (<Button onClick={onDelete}>Delete</Button>)}
                    </Header>
                    {children}
                </>
            )}
        </LoaderHandler>
    )
}

  • Related