16 People Frontend Mutations - jpbarbosa/neo4j-crud GitHub Wiki

People Frontend Mutations

API

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

Mutation Hook

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,
  };
};

Components

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';

Pages

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

Test

open http://localhost:4200
Create Edit

Commit

git add .
git commit -m "People Frontend Mutations"
⚠️ **GitHub.com Fallback** ⚠️