A lightweight, framework-agnostic async data handling library for React & Vue, inspired by Riverpod’s AsyncValue pattern and powered by ts-results-es.
- Unified async state with
AsyncLoading,AsyncError, andAsyncDatawrappingOption<T> - Built-in caching with auto revalidation & manual refresh
- Supports React & Vue with idiomatic hooks/composables
- Reactive
watchdependencies 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
npm i valyncyarn add valyncbun add valyncA 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;}const[state,refetch,setData]=useValync<T>(key,options);AsyncLoading<T>;AsyncError<T>;// contains error { name, message, code? }AsyncData<T>;// contains Option<T>: Some(value) or Noneimport{useValync,AsyncValue}from"@epikoder/valync-react";functionUserProfile({ userId }: {userId: string}){const[state,refetch]=useValync<{name: string;age: number}>(`/api/user/${userId}`,{fetchOnMount: true,retryCount: 2,},);returnstate.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 }),},});import{useValync,AsyncValue}from"@epikoder/valync-vue";import{computed}from"vue";exportdefault{setup(){const[state,refetch]=useValync("/api/user/123",{fetchOnMount: true,});constuserDisplay=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().
importaxiosfrom"axios";import{createValyn}from"valync/react";constuseAxiosValync=createValyn({client: async(url,init)=>awaitaxios({ url, ...init}).then((res)=>{"status": "success";"data": res.data}),// transforms to data});// use in a componentconst[state,refetch,setData]=useAxiosValync<User>("/api/user",{onData: (data)=>{// transform data to User if neededreturndata;},});If this project saved you time, helped you ship faster, or made you say "damn, that's slick!" — consider buying me a beer 🍻