Lädt...


🔧 Building CRUD App with react-form, zod, react data grid, react-query and json-server


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

Goal :

Our goal is to develop a react CRUD application.

Image description

Our stack :

  • react-form
  • zod
  • ag-grid-react
  • react-query
  • json-server

Setup environment :

Create a react project using vite :

npm create vite@latest crud-react -- --template react-ts

Install dependencies :

npm install react-hook-form zod @hookform/resolvers ag-grid-react react-query axios

Create and start Server :

Object (product) structure :

{
    "id": "w38y",
    "name": "Vitamin C Tablets",
    "price": 19.99,
    "expiryDate": "2025-01-01",
    "emailSupplier": "[email protected]"
}

Create a file that contain sample data in /db/db.json :

{
  "products": [
    {
      "id": "w38y",
      "name": "Vitamin C Tablets",
      "price": 19.99,
      "expiryDate": "2025-01-01",
      "emailSupplier": "[email protected]"
    },
    {
      "id": "a99x",
      "name": "Omega-3 Fish Oil",
      "price": 30.99,
      "expiryDate": "2024-11-15",
      "emailSupplier": "[email protected]"
    },
    {
      "id": "x82j",
      "name": "Calcium + Vitamin D",
      "price": 15.5,
      "expiryDate": "2026-06-01",
      "emailSupplier": "[email protected]"
    },
    {
      "id": "a40i",
      "name": "Zinc Lozenges",
      "price": 12.99,
      "expiryDate": "2024-09-30",
      "emailSupplier": "[email protected]"
    },
    {
      "id": "c52f",
      "name": "Probiotic Capsules",
      "price": 25.75,
      "expiryDate": "2025-03-20",
      "emailSupplier": "[email protected]"
    }
  ]
}

Start json-server :

npx json-server db/db.json

Setup react query :

Update /src/App.tsx :

import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
    </QueryClientProvider>
  );
}

export default App;

Create /src/types.ts :

export type FormData = {
  name: string;
  price: number;
  expiryDate: string;
  emailSupplier: string;
};

export type FormFieldNames = "name" | "price" | "expiryDate" | "emailSupplier";  

export type Product = {
  id: string;
  name: string;
  price: number;
  expiryDate: string;
  emailSupplier: string;
};

Create /src/server/productQuery.ts :

import { useMutation, useQuery, useQueryClient } from "react-query";
import { FormData, Product } from "../types";
import axios from "axios";

const URL = "http://localhost:3000";
const PRODUCTS = "products";

export const save = async (product: FormData) =>
  axios.post(`${URL}/${PRODUCTS}`, product);

export const useSave = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (newProduct: FormData) => save(newProduct),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [PRODUCTS] });
    },
  });
};

export const fetch = async () => {
  const result = await axios.get(`${URL}/${PRODUCTS}`);
  return result.data;
};

export const useProducts = () =>
  useQuery<Product[]>({
    queryKey: [PRODUCTS],
    queryFn: fetch,
  });

const remove = async (id: string) => {
  await axios.delete(`${URL}/${PRODUCTS}/${id}`);
};

export const useRemove = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => remove(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [PRODUCTS] });
    },
  });
};

export const update = async (product: Product) =>
  axios.put(`${URL}/${PRODUCTS}/${product.id}`, product);

export const useUpdate = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (product: Product) => update(product),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [PRODUCTS] });
    },
  });
};

Create Form :

Update /src/types.ts:

import { z, ZodType } from "zod";

export type FormData = {
  name: string;
  price: number;
  expiryDate: string;
  emailSupplier: string;
};

export type FormFieldNames = "name" | "price" | "expiryDate" | "emailSupplier";

export const ProductSchema: ZodType<FormData> = z.object({
  name: z.string().min(3),
  price: z.number().min(1).max(1000),
  expiryDate: z
    .string()
    .refine(
      (date) => new Date(date) > new Date(),
      "Expiry Date must be superior than current date",
    ),
  emailSupplier: z.string().email(),
});



export type Product = {
  id: string;
  name: string;
  price: number;
  expiryDate: string;
  emailSupplier: string;
};

Create /src/components/form/FormField.tsx :

import { FieldError, UseFormRegister } from "react-hook-form";
import { FormData, FormFieldNames } from "../../types";

type FormFieldProps = {
  type: string;
  placeholder: string;
  name: FormFieldNames;
  register: UseFormRegister<FormData>;
  error: FieldError | undefined;
  valueAsNumber?: boolean;
  step?: number | string;
};

const FormField = ({
  type,
  placeholder,
  name,
  register,
  error,
  valueAsNumber,
  step,
}: FormFieldProps) => (
  <>
    <input
      type={type}
      placeholder={placeholder}
      step={step}
      {...register(name, { valueAsNumber })}
    />
    {error && <span> {error.message} </span>}
  </>
);

export default FormField;

Create /src/components/form/Form.tsx :

import { useForm } from "react-hook-form";
import FormField from "./FormField";
import { FormData, ProductSchema } from "../../types";
import { zodResolver } from "@hookform/resolvers/zod";
import { useSave } from "../../server/productQuery";

const Form = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(ProductSchema),
  });

  const mutation = useSave();

  const onSubmit = (data: FormData) => {
    mutation.mutate(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <FormField
          type="text"
          placeholder="Name"
          name="name"
          register={register}
          error={errors.name}
        />
        <FormField
          type="number"
          placeholder="Price"
          name="price"
          step="0.01"
          register={register}
          error={errors.price}
          valueAsNumber
        />
        <FormField
          type="date"
          placeholder="Expiry Date"
          name="expiryDate"
          register={register}
          error={errors.expiryDate}
        />

        <FormField
          type="email"
          placeholder="Email"
          name="emailSupplier"
          register={register}
          error={errors.emailSupplier}
        />
        <button type="submit">Add</button>
      </div>
    </form>
  );
};

export default Form;

Create Table :

Create /src/components/table/Products.tsx :

import { useMemo } from "react";
import { Product } from "../../types";
import { useProducts, useRemove, useUpdate } from "../../server/productQuery";

import { AgGridReact } from "ag-grid-react";
import { ColDef, ColGroupDef } from "ag-grid-community";
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-quartz.css";

const Products = () => {
  const { data: products } = useProducts();
  const removeMutation = useRemove();
  const updateMutation = useUpdate();

  const columns = useMemo<(ColDef | ColGroupDef<Product>)[]>(
    () => [
      { field: "id", editable: false },
      { field: "name", editable: true },
      { field: "price", editable: true },
      { field: "expiryDate", editable: true },
      { field: "emailSupplier", editable: true },
      {
        field: "delete",
        sortable: false,
        editable: false,
        cellRenderer: (params: { data: Product }) => (
          <button onClick={() => removeMutation.mutate(params.data.id)}>
            Delete
          </button>
        ),
      },
    ],
    [],
  );

  return (
    <div className="ag-theme-quartz" style={{ height: 500 }}>
      <AgGridReact
        rowData={products}
        columnDefs={columns}
        onCellValueChanged={(params) => {
          updateMutation.mutate(params.data);
        }}
      />
    </div>
  );
};

export default Products;

Update /src/App.tsx :

import { QueryClient, QueryClientProvider } from "react-query";
import Form from "./components/form/Form";
import Products from "./components/table/Products";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Form />
      <Products />
    </QueryClientProvider>
  );
}

export default App;

Git repository :

https://github.com/TesMae/crud-react

Thanks for following along!

...

🔧 Introducing the Zod Schema Designer: A Visual Tool for Creating and Editing Zod Schemas


📈 38.93 Punkte
🔧 Programmierung

🔧 CSS Grid Handbook – Complete Guide to Grid Containers and Grid Items


📈 33.06 Punkte
🔧 Programmierung

🔧 How To Enhance AG Grid with Avatars: Building a Collaborative Grid with React and Ably


📈 31.9 Punkte
🔧 Programmierung

🔧 Building Robust Applications in React with TypeScript and Zod for REST API Validation


📈 29.54 Punkte
🔧 Programmierung

🔧 Building a TypeScript Helper for Mock Data Generation with Zod and Faker


📈 28.62 Punkte
🔧 Programmierung

🕵️ rConfig up to 3.9.4 lib/crud/search.crud.php Command privilege escalation


📈 27.61 Punkte
🕵️ Sicherheitslücken

🔧 FastAPI Beyond CRUD Part 3 - Buiding a CRUD REST API


📈 27.61 Punkte
🔧 Programmierung

🔧 FastAPI Beyond CRUD Part 6 - CRUD With Async SQLModel (An Introduction to Dependency Injection)


📈 27.61 Punkte
🔧 Programmierung

🔧 Introducing saksh-crud: Simplify Your Node.js CRUD Operations


📈 27.61 Punkte
🔧 Programmierung

🔧 Building a Google Sheets–Like Table Component with TanStack Table, Zod, and ShadCN/UI


📈 25.86 Punkte
🔧 Programmierung

🔧 use-magic-grid: Official React port of the magic-grid library


📈 24.87 Punkte
🔧 Programmierung

🔧 Building a Simple CRUD Application with React and PostgreSQL using Docker


📈 24.51 Punkte
🔧 Programmierung

🔧 User authentication and authorization in Node.js, Express.js app, using Typescript, Prisma, Zod and JWT


📈 23.9 Punkte
🔧 Programmierung

🔧 JSON Viewer Pro: The Ultimate Tool for Formatting, Viewing, and Managing JSON Data


📈 23.77 Punkte
🔧 Programmierung

🔧 JSON Diff: Comparing and Identifying Changes in JSON Data


📈 23.77 Punkte
🔧 Programmierung

🔧 Building a Full-Stack CRUD App with Next.js 14, Prisma, and PostgreSQL


📈 23.38 Punkte
🔧 Programmierung

🔧 Making Eleventy Data Traceable with TSX and Zod


📈 22.85 Punkte
🔧 Programmierung

🔧 Validating forms in React Apps with Zod


📈 22.51 Punkte
🔧 Programmierung

🍏 JSON Editor 1.36 - Flexible visual editor for JSON data.


📈 22.51 Punkte
🍏 iOS / Mac OS

🔧 Is there any tool available for JSON where I can manipulate JSON data?


📈 22.51 Punkte
🔧 Programmierung

🔧 Diff JSON – A Complete Guide to Comparing JSON Data


📈 22.51 Punkte
🔧 Programmierung

🔧 JSON Diff: Comparing JSON Data Effectively


📈 22.51 Punkte
🔧 Programmierung

🔧 CSS Grid: Moving From CSS Frameworks To CSS Grid (2018 and beyond)


📈 22.46 Punkte
🔧 Programmierung

🔧 How to Build a Grid to List Toggle using CSS Grid and JavaScript


📈 22.46 Punkte
🔧 Programmierung

🔧 The Difference between Flexbox (flex) and Grid Layout (grid)


📈 22.46 Punkte
🔧 Programmierung

🎥 Observable Flutter #40: Building with Serverpod - Data CRUD


📈 22.33 Punkte
🎥 Video | Youtube

🔧 JSON to PDF Magic: Harnessing LaTeX and JSON for Effortless Customization and Dynamic PDF Generation


📈 22.27 Punkte
🔧 Programmierung

🔧 iter.json: A Powerful and Efficient Way to Iterate and Manipulate JSON in Go


📈 22.27 Punkte
🔧 Programmierung

matomo