queryFields.ts
1 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 import dayjs from "dayjs"; 3 import { getAPIURL } from "./url"; 4 5 export enum FieldType { 6 text = "text", 7 integer = "integer", 8 integer_range = "integer_range", 9 float = "float", 10 float_range = "float_range", 11 datetime = "datetime", 12 boolean = "boolean", 13 choice = "choice", 14 } 15 16 export enum EntityType { 17 vendor = "vendor", 18 filament = "filament", 19 spool = "spool", 20 } 21 22 export interface FieldParameters { 23 name: string; 24 order: number; 25 unit?: string; 26 field_type: FieldType; 27 default_value?: string | (number | null)[] | boolean | dayjs.Dayjs; 28 choices?: string[]; 29 multi_choice?: boolean; 30 } 31 32 export interface Field extends FieldParameters { 33 key: string; 34 entity_type: EntityType; 35 } 36 37 export function useGetFields(entity_type: EntityType) { 38 return useQuery<Field[]>({ 39 queryKey: ["fields", entity_type], 40 queryFn: async () => { 41 const response = await fetch(`${getAPIURL()}/field/${entity_type}`); 42 return response.json(); 43 }, 44 }); 45 } 46 47 export function useSetField(entity_type: EntityType) { 48 const queryClient = useQueryClient(); 49 50 return useMutation<Field[], unknown, { key: string; params: FieldParameters }, { previousFields?: Field[] }>({ 51 mutationFn: async ({ key, params }) => { 52 const response = await fetch(`${getAPIURL()}/field/${entity_type}/${key}`, { 53 method: "POST", 54 headers: { 55 "Content-Type": "application/json", 56 }, 57 body: JSON.stringify(params), 58 }); 59 60 // Throw error if response is not ok 61 if (!response.ok) { 62 throw new Error((await response.json()).message); 63 } 64 65 return response.json(); 66 }, 67 onMutate: async ({ key, params }) => { 68 // Cancel any outgoing refetches (so they don't overwrite our optimistic update) 69 await queryClient.cancelQueries({ 70 queryKey: ["fields", entity_type], 71 }); 72 73 // Snapshot the previous value 74 const previousFields = queryClient.getQueryData<Field[]>(["fields", entity_type]); 75 76 // Optimistically update to the new value 77 queryClient.setQueryData<Field[]>(["fields", entity_type], (old) => { 78 if (!old) { 79 return [ 80 { 81 key: key, 82 entity_type: entity_type, 83 ...params, 84 }, 85 ]; 86 } 87 return old.map((field) => { 88 if (field.key === key) { 89 return { ...field, ...params }; 90 } 91 return field; 92 }); 93 }); 94 95 // Return a context object with the snapshotted value 96 return { previousFields }; 97 }, 98 onError: (_err, _newFields, context) => { 99 // Rollback to the previous value 100 if (context?.previousFields) { 101 queryClient.setQueryData(["fields", entity_type], context.previousFields); 102 } 103 }, 104 onSettled: () => { 105 // Invalidate and refetch 106 queryClient.invalidateQueries({ 107 queryKey: ["fields", entity_type], 108 }); 109 }, 110 }); 111 } 112 113 export function useDeleteField(entity_type: EntityType) { 114 const queryClient = useQueryClient(); 115 116 return useMutation<Field[], unknown, string>({ 117 mutationFn: async (key) => { 118 const response = await fetch(`${getAPIURL()}/field/${entity_type}/${key}`, { 119 method: "DELETE", 120 }); 121 122 // Throw error if response is not ok 123 if (!response.ok) { 124 throw new Error((await response.json()).message); 125 } 126 127 return response.json(); 128 }, 129 onSuccess: () => { 130 // Invalidate and refetch 131 queryClient.invalidateQueries({ 132 queryKey: ["fields", entity_type], 133 }); 134 }, 135 }); 136 }