UNPKG

@hyper-fetch/react

Version:

React hooks and utils for the hyper-fetch

213 lines (171 loc) 7.75 kB
# ⚛️ Hyper Fetch React <p align="center"> <b>React hooks for HyperFetch. Automatic fetching, caching, and real-time updates with full type safety.</b> </p> <p align="center"> <a href="https://bettertyped.com/"> <img src="https://custom-icon-badges.demolab.com/static/v1?label=&message=BetterTyped&color=333&logo=BT" /> </a> <a href="https://github.com/BetterTyped/hyper-fetch"> <img src="https://custom-icon-badges.demolab.com/github/stars/BetterTyped/hyper-fetch?logo=star&color=118ab2" /> </a> <a href="https://github.com/BetterTyped/hyper-fetch/blob/main/License.md"> <img src="https://custom-icon-badges.demolab.com/github/license/BetterTyped/hyper-fetch?logo=law&color=yellow" /> </a> <a href="https://www.npmjs.com/package/@hyper-fetch/react"> <img src="https://custom-icon-badges.demolab.com/npm/v/@hyper-fetch/react.svg?logo=npm&color=e76f51" /> </a> <a href="https://www.npmjs.com/package/@hyper-fetch/react"> <img src="https://custom-icon-badges.demolab.com/npm/dm/@hyper-fetch/react?logoColor=fff&logo=trending-up" /> </a> <a href="https://github.com/BetterTyped/hyper-fetch"> <img src="https://custom-icon-badges.demolab.com/badge/typescript-%23007ACC.svg?logo=typescript&logoColor=white" /> </a> <a href="https://bundlephobia.com/package/@hyper-fetch/react"> <img src="https://custom-icon-badges.demolab.com/bundlephobia/minzip/@hyper-fetch/react?color=64BC4B&logo=package" /> </a> </p> ## 📖 About `@hyper-fetch/react` connects HyperFetch's typed request system to React's component lifecycle. `useFetch` triggers requests on mount and re-fetches when dependencies change. `useSubmit` gives you a `submit` function for mutations triggered by user actions. Both hooks return fully typed `data`, `error`, and loading states with built-in callbacks. ## 🎯 Key Capabilities - ⚡ **Data on mount, zero boilerplate** — `useFetch` fetches, caches, and types your data automatically - 🎯 **Mutations that just work** — `useSubmit` handles loading, errors, and success states for form submissions - 🔄 **Always fresh data** — Auto-refetch on window focus, polling, and stale-time revalidation built in - ✨ **Optimistic updates with rollback** — Update the UI instantly and auto-revert if the request fails - 🔮 **Full type safety** — Every hook, callback, response, and error is typed end-to-end with zero `any` - 🚀 **Instant page loads** — Prefetch data before components mount so users never wait - 🔗 **No more useEffect spaghetti** — Dependent requests via `disabled` option, no manual orchestration - 📡 **SSR out of the box** — Works with Next.js, Remix, and any server-rendered framework - 💎 **Skip the state library** — No Redux or Zustand needed for server stateit's all handled ## 🚀 Quick Start ```bash npm install @hyper-fetch/core @hyper-fetch/react ``` ```tsx import { useFetch } from "@hyper-fetch/react"; import { getUsers } from "./api"; const UserList = () => { // Fetches on mount, returns typed data — no useEffect needed const { data, loading, error } = useFetch(getUsers); if (loading) return <p>Loading...</p>; if (error) return <p>Something went wrong</p>; return <ul>{data?.map((user) => <li key={user.id}>{user.name}</li>)}</ul>; }; ``` ## 📚 Documentation - [Getting Started](https://hyperfetch.bettertyped.com/docs/getting-started/quick-start) - [React Overview](https://hyperfetch.bettertyped.com/docs/react/overview) - [API Reference](https://hyperfetch.bettertyped.com/api/) ## 💡 Examples ### Fetch with callbacks ```tsx import { useFetch } from "@hyper-fetch/react"; const UserProfile = ({ userId }: { userId: number }) => { // setParams makes the request dynamic — re-fetches when userId changes const { data, loading, error, onSuccess, onError } = useFetch( getUser.setParams({ userId }), ); // Typed callbacks — response shape matches your request definition onSuccess(({ response }) => { console.log("Loaded user:", response.data.name); }); onError(({ response }) => { console.error("Failed to load user:", response.error); }); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return <h1>{data.name}</h1>; }; ``` ### Mutations with useSubmit ```tsx import { useSubmit } from "@hyper-fetch/react"; const CreateUserForm = () => { // useSubmit doesn't fire on mount — it waits for you to call submit() const { submit, submitting, onSubmitSuccess, onSubmitError } = useSubmit(createUser); // Callbacks fire after each submission with typed response data onSubmitSuccess(({ response }) => { alert(`User ${response.data.name} created!`); }); onSubmitError(({ response }) => { alert(`Error: ${response.error.message}`); }); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const form = new FormData(e.currentTarget); // Pass typed payload — TypeScript ensures the shape matches your request definition submit({ data: { name: form.get("name") as string, email: form.get("email") as string }, }); }; return ( <form onSubmit={handleSubmit}> <input name="name" placeholder="Name" required /> <input name="email" placeholder="Email" required /> <button type="submit" disabled={submitting}> {submitting ? "Creating..." : "Create User"} </button> </form> ); }; ``` ### Dependent requests ```tsx const UserPosts = ({ userId }: { userId: number }) => { // First request fetches the user const { data: user } = useFetch(getUser.setParams({ userId })); // Second request waits until user is loaded — no useEffect chains needed const { data: posts } = useFetch(getUserPosts.setParams({ userId }), { disabled: !user, }); return ( <div> <h1>{user?.name}</h1> {posts?.map((post) => <article key={post.id}>{post.title}</article>)} </div> ); }; ``` ### Optimistic updates with automatic rollback ```tsx import { useSubmit } from "@hyper-fetch/react"; const AddComment = ({ postId }: { postId: number }) => { // setOptimistic runs BEFORE the request — update the list instantly const optimisticAdd = addComment.setParams({ postId }).setOptimistic((request, client) => { const cacheKey = getComments.setParams({ postId }); const previous = client.cache.get(cacheKey); // Immediately insert the new comment into the cached list const optimisticComment = { id: `temp-${Date.now()}`, text: request.data.text, author: "You" }; client.cache.set(cacheKey, { ...previous, data: [...(previous?.data || []), optimisticComment], }); return { context: { previous }, // If the request fails, put the old list back rollback: () => client.cache.set(cacheKey, previous), // On success, refetch to get the real server data invalidate: [cacheKey], }; }); const { submit, submitting, onSubmitError } = useSubmit(optimisticAdd); // mutationContext is fully typed from your setOptimistic return onSubmitError(({ mutationContext }) => { console.log("Rolled back — restored", mutationContext?.previous?.data?.length, "comments"); }); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const text = new FormData(e.currentTarget).get("text") as string; submit({ data: { text } }); e.currentTarget.reset(); }; return ( <form onSubmit={handleSubmit}> <input name="text" placeholder="Write a comment..." required /> <button type="submit" disabled={submitting}>Post</button> </form> ); }; ``` ## License [MIT](https://github.com/BetterTyped/hyper-fetch/blob/main/License.md)