I have been working on a project where the user is able to create multiple-choice quizzes. I have the database models which define Quiz, Question, and Option:
Option = {
id: number;
text: string;
point_value: number;
question_id: number;
}
Question = {
id: number;
text: string;
options: Option[]
quiz_id: number;
}
Quiz = {
id: number;
title: string;
questions: Question[]
}
In my app I have tried two methods of fetching and mutating this data:
- Creating API routes for each model, where posting to that route with a matching JSON Schema will compare that to the current state of that database model and commit any changes.
- Creating API routes for getting and updating each individual attribute.
Both of these methods feel wrong, as they require a lot of (sometimes redundant) API calls constantly and have made it incredibly hard to allow for optimistic updates.
Here is a basic Quiz Editor component in its current state. It is only responsible for rendering a list of QuestionCard, where each QuestionCard has QueryInput components that link editable attributes of each question (text, option.text, and option.point_value) to the database:
// Editor.tsx
function Editor() {
const { quiz } = useQuizContext();
return (
{quiz.questions.map((question) => (
<div>
<QuestionCard question={question}/>
</div>
))}
)
}
// QueryInput.tsx
type QueryInputProps = {
id: number
fetchFunction: (id: number) => Promise<any>
updateFunction: (id: number, value: string) => Promise<any>
}
function QueryInput({ id, fetchFunction, updateFunction }: QueryInputProps) {
const queryClient = useQueryClient()
const query = useSuspenseQuery({
queryKey: [fetchFunction.name, id]
queryFn: () => fetchFunction(id)
})
const mutation = useMutation({
mutationFn: (newValue: string) => updateFunction(id, newValue)
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [fetchFunction.name, id]
})
}
})
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
newValue = e.target.value
mutation.mutate(newValue)
}
return (
<input
defaultValue={query.data}
onChange={handleChange}
/>
)
}
My gut tells me to take advantage of Immer and the QueryCache to handle all (nested) data modification, but I don't understand how to reflect these changes to my API.
If anyone can offer help with this solution or suggest an alternative, I would be very grateful. Thanks!