Valync
A lightweight, framework-agnostic async data handling library for React & Vue, inspired by Riverpod’s AsyncValue pattern and powered by ts-results-es.
Features
- Unified async state with
AsyncLoading
,AsyncError
, andAsyncData
wrappingOption<T>
- Built-in caching with auto revalidation & manual refresh
- Supports React & Vue with idiomatic hooks/composables
- Reactive
watch
dependencies to refetch on data change setData()
for manual or partial UI updates- Uses a strict API response shape for uniform error & data handling
- Flexible Mutations / Write operations to API
Installation
Npm
npm i valync
yarn
yarn add valync
Bun
bun add valync
API Overview
useValync<T>(key, options?)
A hook/composable to fetch and manage async data.
key: string | Record<string, any>
— Unique cache key or URL.options?
:
{
init?: Omit<RequestInit, "signal">; // Pass request options for default client or custom client
cache?: boolean; // default true, enable/disable caching
fetchOnMount?: boolean; // default true, fetch automatically on mount
retryCount?: number; // retry count for failed requests
onData?: (data: T) => T; // transform data before setting
watch?: any[]; // reactive dependencies to trigger refetch
initialData?: ApiResponse<T>; // initial server-side data for hydration
fetchInterval?: number;
onSuccess?: (data: T) => void;
onError?: (err: ApiErrorResponse["error"]) => void;
}
Return tuple
const [state, refetch, setData] = useValync<T>(key, options);
AsyncValue States
AsyncLoading<T>;
AsyncError<T>; // contains error { name, message, code? }
AsyncData<T>; // contains Option<T>: Some(value) or None
Example usage
React
import { useValync, AsyncValue } from "@epikoder/valync-react";
function UserProfile({ userId }: { userId: string }) {
const [state, refetch] = useValync<{ name: string; age: number }>(
`/api/user/${userId}`,
{
fetchOnMount: true,
retryCount: 2,
},
);
return state.when({
loading: () => <div>Loading...</div>,
error: (err) => <div>Error: {err.message}</div>,
data: (opt) =>
opt.some ? (
<div>
{opt.val.name} ({opt.val.age} years old)
</div>
) : (
<div>No data available</div>
),
});
}
const [state, onLogin] = useValync("/api/auth", {
cache: false,
fetchOnMount: false,
init: {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
},
});
Vue 3
import { useValync, AsyncValue } from "@epikoder/valync-vue";
import { computed } from "vue";
export default {
setup() {
const [state, refetch] = useValync("/api/user/123", {
fetchOnMount: true,
});
const userDisplay = computed(() =>
state.when({
loading: () => "Loading...",
error: (e) => `Error: ${e.message}`,
data: (opt) => (opt.some ? `${opt.val.name}` : "No user"),
}),
);
return { userDisplay, refetch };
},
};
for use within template you can use state.isLoading()
, state.isData()
and state.isError()
.
For more usage check the examples
Axios or other HTTP client
import axios from "axios";
import { createValyn } from "valync/react";
const useAxiosValync = createValyn({
client: async (url, init) =>
await axios({ url, ...init }).then((res) => {"status": "success"; "data": res.data}), // transforms to data
});
// use in a component
const [state, refetch, setData] = useAxiosValync<User>("/api/user", {
onData: (data) => {
// transform data to User if needed
return data;
},
});
☕️ Buy Me a Drink
If this project saved you time, helped you ship faster, or made you say "damn, that's slick!" — consider buying me a beer 🍻