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>
)
}