vikejs / vike-react

🔨 React integration for Vike
https://vike.dev/vike-react
MIT License
94 stars 15 forks source link

vike-react-telefunc #131

Open nitedani opened 1 month ago

nitedani commented 1 month ago

Ideas for vike-react-telefunc

Right now, to integrate telefunc and @tanstack/react-query, we need to write some boilerplate for query invalidation:

// fruits.telefunc.ts
const db = [
  {
    id: 1,
    name: "Apple",
  },
  {
    id: 2,
    name: "Orange",
  },
];

export async function addFruit(fruit: { id: number; name: string }) {
  db.push(fruit);
}

export async function updateFruit(input: { id: number; name: string }) {
  const fruit = db.find((fruit) => fruit.id === input.id);
  if (fruit) {
    fruit.name = input.name;
  }
  return fruit;
}

export async function getFruit(id: number) {
  return db.find((fruit) => fruit.id === id);
}

export async function getFruits() {
  return db;
}
// +Page.tsx

import {
  useSuspenseQuery,
  useMutation,
  useQueryClient,
} from "@tanstack/react-query";
import { getFruits, getFruit, addFruit, updateFruit } from "./fruits.telefunc";

const useFruits = () =>
  useSuspenseQuery({
    queryFn: getFruits,
    queryKey: ["fruits"],
  });

const useFruit = (id: number) =>
  useSuspenseQuery({
    queryFn: () => getFruit(id),
    queryKey: ["fruits", id],
  });

const useAddFruit = () => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: addFruit,
    onSettled() {
      queryClient.invalidateQueries({ queryKey: ["fruits"] });
    },
  });
};

const useUpdateFruit = () => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: updateFruit,
    onSettled() {
      queryClient.invalidateQueries({ queryKey: ["fruits"] });
    },
  });
};

It would be nice, if this integration could be simplified, for example:

// fruits.telefunc.ts

import { invalidates } from "vike-react-telefunc"

const db = [
  {
    id: 1,
    name: "Apple",
  },
  {
    id: 2,
    name: "Orange",
  },
];

invalidates(getFruits);
invalidates(getFruit); // invalidates all getFruit queries
export async function addFruit(fruit: { id: number; name: string }) {
  db.push(fruit);
}

invalidates((input) => [getFruit, input.id]); // invalidates a single getFruit query
export async function updateFruit(input: { id: number; name: string }) {
  const fruit = db.find((fruit) => fruit.id === input.id);
  if (fruit) {
    fruit.name = input.name;
  }
  return fruit;
}

// queryKey: ['/src/pages/index/fruits.telefunc.ts:getFruit', id]
export async function getFruit(id: number) {
  return db.find((fruit) => fruit.id === id);
}

// queryKey: ['/src/pages/index/fruits.telefunc.ts:getFruits']
export async function getFruits() {
  return db;
}
//+Page.tsx

import { useQuery, useMutation } from "vike-react-telefunc";
import { getFruits, getFruit, addFruit, updateFruit } from "./fruits.telefunc";

const useFruits = () => useQuery(getFruits);
const useFruit = (id: number) => useQuery(getFruit, id);
const useAddFruit = () => useMutation(addFruit);
const useUpdateFruit = () => useMutation(updateFruit);

What do you think?

brillout commented 1 month ago

Yes, that would be wonderful.

How about something like this?

cache(getFruits, ['fruits'])
export async function addFruit(fruit: { id: number; name: string }) {
  db.push(fruit);
}

cache(updateFruit, input => ['fruit', input.id])
export async function updateFruit(input: { id: number; name: string }) {
  const fruit = db.find((fruit) => fruit.id === input.id);
  if (fruit) {
    fruit.name = input.name;
  }
  return fruit;
}

cache(getFruit, input => ['fruit', input.id])
export async function getFruit(id: number) {
  return db.find((fruit) => fruit.id === id);
}

cache(getFruits, ['fruits'])
export async function getFruits() {
  return db;
}

The cache is invalidated when useMutation() is used.

I feel like invalidating over a cache key is more robust than directly invalidating telefunctions? It's also more TanStack Query idiomatic I think?

Also, maybe we can name it vike-react-telefunc-query for now (I want to experiment with a separate extension that doesn't use TanStack Query). I'm not sure yet which one will end up being the main extension. (Ideally, we can have a single vike-react-telefunc with an optional features/peer dependency with vike-react-query, but I don't know how feasible that is.)

brillout commented 1 month ago

(Maybe we shouldn't name it cache() but key() or queryKey() to better align with TanStack Query.)