-
Notifications
You must be signed in to change notification settings - Fork 1
11 Frontend Mutations
JP Barbosa edited this page Apr 15, 2023
·
1 revision
code ./packages/web/src/api/movies.ts
import axios from 'axios';
import { Movie } from '@neo4j-crud/shared';
const url = `${import.meta.env.VITE_API_URL}/movies`;
export const movies = {
getAll: (search: string) =>
axios.get<Movie[]>(`${url}?search=${search}`).then((res) => res.data),
getById: (id: number) =>
axios.get<Movie>(`${url}/${id}`).then((res) => res.data),
create: (movie: Movie) =>
axios.post<Movie>(url, movie).then((res) => res.data),
update: (id: number, movie: Movie) =>
axios.put<Movie>(`${url}/${id}`, movie).then((res) => res.data),
remove: (id: number) =>
axios.delete<Movie>(`${url}/${id}`).then((res) => res.data),
};
code ./packages/web/src/api/index.ts
export * from './movies';
code ./packages/web/src/hooks/useMovieMutation.ts
import { MutationKey, useMutation } from 'react-query';
import { AxiosCustomError, Movie } from '@neo4j-crud/shared';
import * as api from '../api';
export const useMovieMutation = (
mutationKey: MutationKey,
callback: (message: string) => void
) => {
const upsert = useMutation<Movie, AxiosCustomError, Movie>(
mutationKey,
(movie) => {
if (movie.id) {
return api.movies.update(movie.id, movie);
} else {
return api.movies.create(movie);
}
},
{
onSuccess: () => {
callback(`Movie created/updated successfully`);
},
}
);
const remove = useMutation<Movie | void, AxiosCustomError, Movie>(
mutationKey,
(movie) => {
if (movie.id) {
return api.movies.remove(movie.id);
} else {
return Promise.resolve();
}
},
{
onSuccess: () => {
callback(`Movie deleted successfully`);
},
}
);
const isSuccess = upsert.isSuccess || remove.isSuccess;
const error = upsert.error || remove.error;
return {
upsert,
remove,
isSuccess,
error,
};
};
code ./packages/web/src/pages/movies/index.tsx
import { Route, Routes } from 'react-router-dom';
import { List } from './List';
import { Edit } from './Edit';
import { New } from './New';
export const Movies = () => {
return (
<Routes>
<Route path="/" element={<List />} />
<Route path="/:id/edit" element={<Edit />} />
<Route path="/new" element={<New />} />
</Routes>
);
};
code ./packages/web/src/pages/movies/List/index.tsx
...
import { Link } from 'react-router-dom';
export const List = () => {
...
return (
<div>
<div className="actions-bar">
<h2>Movies</h2>
<div className="filter">
...
</div>
<div>
<Link to="new" className="button primary">
Create Movie
</Link>
</div>
</div>
<NavigationAlert />
<Content search={search} />
</div>
);
};
code ./packages/web/src/pages/movies/Edit.tsx
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
import { AxiosCustomError, Movie } 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<Movie, AxiosCustomError>(
['movies', id],
() => api.movies.getById(Number(id))
);
if (error || isLoading || !data) {
return <AlertCombo error={error} isLoading={isLoading} noData={!data} />;
}
return <Form movie={data} />;
};
code ./packages/web/src/pages/movies/New.tsx
import { Form } from './Form';
export const New = () => {
return <Form />;
};
code ./packages/web/src/pages/movies/Form/index.tsx
import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Link, useNavigate } from 'react-router-dom';
import { Movie } from '@neo4j-crud/shared';
import { useMovieMutation } from '../../../hooks/useMovieMutation';
import { ErrorAlert } from '../../../components';
type FormProps = {
movie?: Movie;
};
export const Form: React.FC<FormProps> = ({ movie }) => {
const navigate = useNavigate();
const defaultValues: Movie = useMemo(
() =>
movie || {
title: '',
tagline: '',
released: 0,
people: {
actors: [],
directors: [],
producers: [],
writers: [],
reviewers: [],
},
},
[movie]
);
const callback = (message: string) => {
return navigate('/movies', {
state: {
message,
},
});
};
const { upsert, remove, error } = useMovieMutation(
['movies', movie?.id],
callback
);
const { handleSubmit, control, reset } = useForm<Movie>({
defaultValues,
});
useEffect(() => {
reset(defaultValues);
}, [reset, defaultValues]);
return (
<div>
<div className="actions-bar">
<h2>{movie ? 'Edit' : 'Create'} Movie</h2>
<Link to="/movies" className="button">
Back to Movies
</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>Title</label>
<Controller
name="title"
control={control}
rules={{ required: true }}
render={(props) => <input type="text" {...props.field} />}
/>
</div>
<div>
<label>Tagline</label>
<Controller
name="tagline"
control={control}
rules={{ required: true }}
render={(props) => <input type="text" {...props.field} />}
/>
</div>
<div>
<label>Released</label>
<Controller
name="released"
control={control}
rules={{ required: true }}
render={(props) => <input type="text" {...props.field} className="number" />}
/>
</div>
</fieldset>
<div className="bottom-actions-bar">
<input type="submit" />
{movie && (
<button
type="button"
className="danger"
onClick={() => remove.mutate(movie)}
>
Delete
</button>
)}
</div>
</form>
</div>
</div>
);
};
code ./packages/web/src/pages/movies/List/Content.tsx
...
import * as api from '../../../api';
...
export const Content: React.FC<ContentProps> = ({ search }) => {
...
const { data, error, isLoading } = useQuery<Movie[], AxiosCustomError>(
['movies', debouncedSearch],
() => api.movies.getAll(search)
);
...
};
Obs.: import axios
and url
are not used anymore, so we can remove them.
code ./packages/web/src/pages/movies/List/index.tsx
...
import { NavigationAlert } from '../../../components';
export const List = () => {
const [search, setSearch] = useState<string>('');
return (
<div>
<div className="actions-bar">
...
</div>
<NavigationAlert />
<Content search={search} />
</div>
);
};
open http://localhost:4200
Create | Edit |
git add .
git commit -m "Frontend Mutations"