netwrap
v4.0.0
Published
A package wrapper for helping teams around the world deliver consistent results when building react applications that connect to backend services. This package inspiration came from working in teams where frontend users could not achieve consistent result
Maintainers
Readme
netwrap
Lightweight request helpers with a React hook and a plain fetcher.
Installation
yarn add netwrapDevelopment commands
From the repo root:
# install workspace deps
yarn install
# build the netwrap package (ESM + CJS)
yarn workspace netwrap build
# run netwrap tests
yarn workspace netwrap test
# deploy netwrap (builds then publishes)
yarn workspace netwrap deploy
# run the Next.js web app
yarn workspace web dev
# build and start the Next.js web app
yarn workspace web build
yarn workspace web start
# run the API demo script
node apps/api/index.jsPeer Dependencies
react(only required when usinguseFetcher)
Usage
Entry points
netwrap: server-safe utilities andfetchernetwrap/client: React hooks (client components only)
Warnings and deprecations
useFetchermust be imported fromnetwrap/clientand used only in client components.- Client-component usage of
fetcheris deprecated and will be removed in the next version. UseuseFetcherinstead. fetcheris not reactive; do not destructuredataorerrorif you expect live updates.fetchercache is instance-local. Recreate the instance and the cache resets.
useFetcher (React client component)
"use client";
import { useFetcher } from "netwrap/client";
import { useState } from "react";
export default function ClientPage() {
const [log, setLog] = useState<string[]>([]);
const { trigger, data, error, isLoading, invalidateCache } = useFetcher<
void,
{ id: number; title: string }
>({
queryFn: async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
return res.json();
},
onStartQuery: () => setLog((prev) => ["start", ...prev]),
onSuccess: () => setLog((prev) => ["success", ...prev]),
onError: () => setLog((prev) => ["error", ...prev]),
onFinal: () => setLog((prev) => ["final", ...prev]),
});
return (
<section>
<h1>Client useFetcher test</h1>
<p>Click to trigger a client-side fetch and watch state updates.</p>
<div className="controls">
<button onClick={() => trigger()} disabled={isLoading}>
{isLoading ? "Loading..." : "Fetch data"}
</button>
<button onClick={() => invalidateCache()} disabled={isLoading}>
Clear cache
</button>
</div>
<div className="status">
<div>Status: {isLoading ? "loading" : "idle"}</div>
{error ? <div className="error">Error</div> : null}
{data ? (
<div>
Result: #{data.id} - {data.title}
</div>
) : (
<div>No data yet</div>
)}
</div>
<div className="events">
<strong>Event log</strong>
<ul>
{log.slice(0, 6).map((item, idx) => (
<li key={`${item}-${idx}`}>{item}</li>
))}
</ul>
</div>
</section>
);
}fetcher (client component, deprecated)
"use client";
import { fetcher } from "netwrap";
import { useState } from "react";
export default function ClientFetcherPage() {
const [log, setLog] = useState<string[]>([]);
const [api] = useState(() =>
fetcher<void, { id: number; title: string }>({
queryFn: async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
return res.json();
},
onStartQuery: () => setLog((prev) => ["start", ...prev]),
onSuccess: () => setLog((prev) => ["success", ...prev]),
onError: () => setLog((prev) => ["error", ...prev]),
onFinal: () => setLog((prev) => ["final", ...prev]),
}),
);
return (
<section>
<h1>Client fetcher test</h1>
<p>Using fetcher in a client component with a stable instance.</p>
<div className="controls">
<button onClick={() => api.trigger()} disabled={api.isLoading}>
{api.isLoading ? "Loading..." : "Fetch data"}
</button>
<button onClick={() => api.invalidateCache()} disabled={api.isLoading}>
Clear cache
</button>
</div>
<div className="status">
<div>Status: {api.isLoading ? "loading" : "idle"}</div>
{api.error ? <div className="error">Error</div> : null}
{api.data ? (
<div>
Result: #{api.data.id} - {api.data.title}
</div>
) : (
<div>No data yet</div>
)}
</div>
<div className="events">
<strong>Event log</strong>
<ul>
{log.slice(0, 6).map((item, idx) => (
<li key={`${item}-${idx}`}>{item}</li>
))}
</ul>
</div>
</section>
);
}API
fetcher(options)
Returns:
trigger(triggerData?): executes the requestdata: last response data (if successful)error: last error (if any)isLoading: loading stategetData(): get the latest data without subscribinggetError(): get the latest error without subscribinggetIsLoading(): get the latest loading state without subscribingonLoadingChange(listener): subscribe to loading state changesonDataChange(listener): subscribe to data changesonErrorChange(listener): subscribe to error changesinvalidateCache(): clears cached response data
Note: fetcher is not reactive. Avoid destructuring data or error from it if you expect live updates; access them from the returned object (api.data) or use the change listeners.
useFetcher(options)
Returns:
trigger(triggerData?): executes the requestdata: last response data (if successful)error: last error (if any)isLoading: loading stateinvalidateCache(): clears cached response data
Options
Both fetcher and useFetcher accept:
queryFn(required): async function that performs the requestonStartQuery: called before the request begins; return a value to overridetriggerdata (sync or async)onSuccess: called with the response dataonError: called with the erroronFinal: called when the request completes (success or error)
Examples
Request with parameters
const { trigger } = fetcher({
queryFn: async (id: number) => {
const res = await fetch(`/api/items/${id}`);
return res.json();
},
});
await trigger(123);Cache behavior (fetcher)
const { trigger, invalidateCache } = fetcher({
queryFn: async () => {
const res = await fetch("/api/value");
return res.json();
},
});
await trigger(); // fetches
await trigger(); // returns cached response
invalidateCache();
await trigger(); // fetches againCache behavior (useFetcher)
const { trigger, invalidateCache } = useFetcher({
queryFn: async () => {
const res = await fetch("/api/value");
return res.json();
},
});
await trigger(); // fetches
await trigger(); // returns cached response
invalidateCache();
await trigger(); // fetches againContributing
- Fork and clone the repo.
- Install dependencies with
yarn install. - Run tests with
yarn workspace netwrap test. - Open a PR with a clear description of the change.
License
MIT
