People Frontend Mutations
code ./packages/web/src/api/people.ts
...
export const people = {
...
create: (person: Person) =>
axios.post<Person>(url, person).then((res) => res.data),
update: (id: number, person: Person) =>
axios.put<Person>(`${url}/${id}`, person).then((res) => res.data),
remove: (id: number) =>
axios.delete<Person>(`${url}/${id}`).then((res) => res.data),
};
code ./packages/web/src/hooks/usePersonMutation.ts
import { MutationKey, useMutation } from 'react-query';
import { AxiosCustomError, Person } from '@neo4j-crud/shared';
import * as api from '../api';
export const usePersonMutation = (
mutationKey: MutationKey,
callback: (message: string) => void
) => {
const upsert = useMutation<Person, AxiosCustomError, Person>(
mutationKey,
(person) => {
if (person.id) {
return api.people.update(person.id, person);
} else {
return api.people.create(person);
}
},
{
onSuccess: () => {
callback(`Person created/updated successfully`);
},
}
);
const remove = useMutation<Person | void, AxiosCustomError, Person>(
mutationKey,
(person) => {
if (person.id) {
return api.people.remove(person.id);
} else {
return Promise.resolve();
}
},
{
onSuccess: () => {
callback(`Person deleted successfully`);
},
}
);
const isSuccess = upsert.isSuccess || remove.isSuccess;
const error = upsert.error || remove.error;
return {
upsert,
remove,
isSuccess,
error,
};
};
code ./packages/web/src/components/InputText.tsx
import {
ControllerFieldState,
ControllerRenderProps,
UseFormStateReturn,
} from 'react-hook-form';
import { FormFieldError } from './FormFieldError';
type InputTextProps = {
field: ControllerRenderProps<any, any>;
fieldState: ControllerFieldState;
formState: UseFormStateReturn<any>;
className?: string;
};
export const InputText: React.FC<InputTextProps> = ({
field,
fieldState,
className,
}) => {
return (
<div>
<input type="text" {...field} className={className || ''} />
<FormFieldError error={fieldState.error} />
</div>
);
};
code ./packages/web/src/components/FormFieldError.tsx
import { FieldError } from 'react-hook-form';
type FormFieldsErrorProps = {
error?: FieldError;
};
export const FormFieldError: React.FC<FormFieldsErrorProps> = ({ error }) => {
if (!error) {
return null;
}
return (
<div className="form-field-error">
<div>{error.type}</div>
<div>{error.message}</div>
</div>
);
};
code ./packages/web/src/components/index.ts
...
export * from './FormFieldError';
export * from './InputText';
code ./packages/web/src/pages/people/index.tsx
...
import { Edit } from './Edit';
import { New } from './New';
export const People = () => {
return (
<Routes>
...
<Route path="/:id/edit" element={<Edit />} />
<Route path="/new" element={<New />} />
</Routes>
);
};
code ./packages/web/src/pages/people/New.tsx
import { Form } from './Form';
export const New = () => {
return <Form />;
};
code ./packages/web/src/pages/people/Edit.tsx
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
import { AxiosCustomError, Person } from '@neo4j-crud/shared';
import * as api from '../../api';
import { AlertCombo } from '../../components';
import { Form } from './Form';
export const Edit = () => {
const params = useParams();
const id = params.id ? parseInt(params.id) : undefined;
const { data, error, isLoading } = useQuery<Person, AxiosCustomError>(
['people', id],
() => api.people.getById(Number(id))
);
if (error || isLoading || !data) {
return <AlertCombo error={error} isLoading={isLoading} noData={!data} />;
}
return <Form person={data} />;
};
code ./packages/web/src/pages/people/Form/index.tsx
import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Link, useNavigate } from 'react-router-dom';
import { Person } from '@neo4j-crud/shared';
import { usePersonMutation } from '../../../hooks/usePersonMutation';
import { ErrorAlert, InputText } from '../../../components';
type FormProps = {
person?: Person;
};
export const Form: React.FC<FormProps> = ({ person }) => {
const navigate = useNavigate();
const defaultValues: Person = useMemo(
() =>
person || {
name: '',
born: 0,
},
[person]
);
const { handleSubmit, control, reset } = useForm<Person>({
defaultValues,
});
const callback = (message: string) => {
return navigate('/people', {
state: {
message,
},
});
};
const { upsert, remove, error } = usePersonMutation(
['people', person?.id],
callback
);
useEffect(() => {
reset(defaultValues);
}, [reset, defaultValues]);
return (
<div>
<div className="actions-bar">
<h2>{person ? 'Edit' : 'Create'} Person</h2>
<Link to="/people" className="button">
Back to People
</Link>
</div>
{error && <ErrorAlert error={error} />}
<div className="pd-16">
<form onSubmit={handleSubmit((data) => upsert.mutate(data))}>
<fieldset className="basic-info">
<legend>Basic Info</legend>
<div>
<label>Name</label>
<Controller
name="name"
control={control}
rules={{ required: true }}
render={(props) => <InputText {...props} />}
/>
</div>
<div>
<label>Born</label>
<Controller
name="born"
control={control}
rules={{ required: false }}
render={(props) => <InputText {...props} className="number" />}
/>
</div>
</fieldset>
<div className="bottom-actions-bar">
<input type="submit" />
{person && (
<button
type="button"
className="danger"
onClick={() => remove.mutate(person)}
>
Delete
</button>
)}
</div>
</form>
</div>
</div>
);
};
open http://localhost:4200
| Create |
Edit |
 |
 |
git add .
git commit -m "People Frontend Mutations"