Gathering detailed insights and metrics for @tobq/loadable
Gathering detailed insights and metrics for @tobq/loadable
Gathering detailed insights and metrics for @tobq/loadable
Gathering detailed insights and metrics for @tobq/loadable
npm install @tobq/loadable
Typescript
Module System
Node Version
NPM Version
TypeScript (99.15%)
JavaScript (0.85%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
18 Stars
96 Commits
2 Watchers
1 Branches
1 Contributors
Updated on Jun 24, 2025
Latest Version
2.0.3
Package Id
@tobq/loadable@2.0.3
Unpacked Size
93.20 kB
Size
20.89 kB
File Count
11
NPM Version
8.11.0
Node Version
19.2.0
Published on
Jan 13, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
Below is an updated README that includes a section on caching. It starts with the basics and gradually introduces the concept of a loading token, then covers how to leverage caching (using in-memory, localStorage
, or indexedDB
).
A lightweight, type-safe, and composable library for managing asynchronous data in React. Loadable provides hooks and utilities to make fetching data clean, declarative, and free from repetitive “loading” and “error” state boilerplate. It’s an alternative to manually writing useState + useEffect
or using heavier data-fetching libraries.
React doesn’t come with an official solution for data fetching, which often leads to repetitive patterns:
Loadable unifies these concerns:
useLoadable
, useThen
, etc.) to chain and compose fetches.1npm install @tobq/loadable 2# or 3yarn add @tobq/loadable
A Loadable<T>
can be:
loading
symbol (or an optional “loading token”).T
.LoadError
object describing the failure.This single union type replaces the typical isLoading
/ data
/ error
triple.
Below is a minimal comparison of how you might load data with and without Loadable:
1function Properties() { 2 const [properties, setProperties] = useState<Property[] | null>(null) 3 const [isLoading, setLoading] = useState(true) 4 5 useEffect(() => { 6 getPropertiesAsync() 7 .then((props) => { 8 setProperties(props) 9 setLoading(false) 10 }) 11 .catch(console.error) 12 }, []) 13 14 if (isLoading || !properties) { 15 return <div>Loading…</div> 16 } 17 return ( 18 <div> 19 {properties.map((p) => ( 20 <PropertyCard key={p.id} property={p} /> 21 ))} 22 </div> 23 ) 24}
1import { useLoadable, hasLoaded } from "@tobq/loadable" 2 3function Properties() { 4 const properties = useLoadable(() => getPropertiesAsync(), []) 5 6 if (!hasLoaded(properties)) { 7 return <div>Loading…</div> 8 } 9 return ( 10 <div> 11 {properties.map((p) => ( 12 <PropertyCard key={p.id} property={p} /> 13 ))} 14 </div> 15 ) 16}
properties
starts as loading
and becomes the loaded data when ready.hasLoaded(properties)
ensures the data is neither loading nor an error.1import { useLoadable, useThen, hasLoaded } from "@tobq/loadable" 2 3function UserProfile({ userId }) { 4 // First load the user 5 const user = useLoadable(() => fetchUser(userId), [userId]) 6 7 // Then load the user’s posts, using the loaded `user` 8 const posts = useThen(user, (u) => fetchPostsForUser(u.id)) 9 10 if (!hasLoaded(user)) return <div>Loading user…</div> 11 if (!hasLoaded(posts)) return <div>Loading posts…</div> 12 13 return ( 14 <div> 15 <h1>{user.name}</h1> 16 {posts.map((p) => ( 17 <Post key={p.id} {...p} /> 18 ))} 19 </div> 20 ) 21}
Use useAllThen
or the all()
helper to coordinate multiple loadable values:
1import { useAllThen, hasLoaded } from "@tobq/loadable"
2
3function Dashboard() {
4 const user = useLoadable(() => fetchUser(), [])
5 const stats = useLoadable(() => fetchStats(), [])
6
7 // Wait for both to be loaded, then call `fetchDashboardSummary()`
8 const summary = useAllThen(
9 [user, stats],
10 (u, s, signal) => fetchDashboardSummary(u.id, s.range, signal),
11 []
12 )
13
14 if (!hasLoaded(summary)) return <div>Loading Dashboard…</div>
15
16 return <DashboardSummary {...summary} />
17}
useLoadable(fetcher, deps, options?)
Loadable<T>
by calling the async fetcher
.useThen(loadable, fetcher, deps?, options?)
useAllThen(loadables, fetcher, deps?, options?)
fetcher
.useLoadableWithCleanup(fetcher, deps, options?)
useLoadable
, but returns [Loadable<T>, cleanupFunc]
for manual aborts.Helpers include:
hasLoaded(loadable)
loadFailed(loadable)
all(...)
map(...)
toOptional(...)
orElse(...)
isUsable(...)
Before:
1const [data, setData] = useState<T | null>(null) 2const [loading, setLoading] = useState(true) 3const [error, setError] = useState<Error | null>(null) 4 5useEffect(() => { 6 setLoading(true) 7 getData() 8 .then(res => setData(res)) 9 .catch(err => setError(err)) 10 .finally(() => setLoading(false)) 11}, [])
After:
1import { useLoadable, loadFailed, hasLoaded } from "@tobq/loadable" 2 3const loadable = useLoadable(() => getData(), []) 4 5if (loadFailed(loadable)) { 6 return <ErrorComponent error={loadable} /> 7} 8if (!hasLoaded(loadable)) { 9 return <LoadingSpinner /> 10} 11 12return <RenderData data={loadable} />
Before:
1useEffect(() => { 2 let cancelled = false 3 4 getUser().then(user => { 5 if (!cancelled) { 6 setUser(user) 7 getUserPosts(user.id).then(posts => { 8 if (!cancelled) { 9 setPosts(posts) 10 } 11 }) 12 } 13 }) 14 15 return () => { cancelled = true } 16}, [])
After:
1const user = useLoadable(() => getUser(), [])
2const posts = useThen(user, (u) => getUserPosts(u.id))
By default, if a fetch fails, useLoadable
returns a LoadError
. You can handle or display it:
1const users = useLoadable(fetchUsers, [], { 2 onError: (error) => console.error("Error loading users:", error) 3}) 4 5if (loadFailed(users)) { 6 return <ErrorBanner error={users} /> 7} 8if (!hasLoaded(users)) { 9 return <Spinner /> 10} 11 12return <UsersList items={users} />
By default, Loadable uses a single symbol loading
to represent the “loading” state. If you need unique tokens for better debugging or timestamp tracking, you can opt for the class-based token:
1import { LoadingToken, newLoadingToken } from "@tobq/loadable" 2 3const token = newLoadingToken() // brand-new token with a timestamp
You can store additional metadata (like startTime
) in the token. Internally, the library handles both loading
(symbol) and LoadingToken
interchangeably.
Loadable supports optional caching of fetched data, allowing you to bypass refetching if the data already exists in memory, localStorage, or indexedDB.
cache
in useLoadable
Within the options
object passed to useLoadable
, you can include:
1cache?: string | { 2 key: string 3 store?: "memory" | "localStorage" | "indexedDB" 4}
cache: "myDataKey"
):
"localStorage"
for storage.cache: { key: "myDataKey", store: "indexedDB" }
):
1function MyComponent() { 2 // #1: Simple string for cache => defaults to localStorage 3 const dataLoadable = useLoadable(fetchMyData, [], { 4 cache: "myDataKey", 5 hideReload: false, 6 onError: (err) => console.error("Load error:", err), 7 }) 8 9 if (dataLoadable === loading) { 10 return <div>Loading...</div> 11 } 12 if (!hasLoaded(dataLoadable)) { 13 // must be an error 14 return <div>Error: {dataLoadable.message}</div> 15 } 16 17 return <pre>{JSON.stringify(dataLoadable, null, 2)}</pre> 18}
The first time the component mounts, it checks localStorage["myDataKey"]
.
hideReload
or your logic).memory
: A global in-memory map (fast, but resets on page refresh).localStorage
: Persists across refreshes, limited by localStorage size (~5MB in many browsers).indexedDB
: Can store larger data more efficiently, though usage is a bit more complex.hideReload: true
means you don’t revert to a “loading” state once something is cached; you only show the old data until the new fetch finishes.useEffect
: Often leads to repetitive loading booleans and tricky cleanup logic. Loadable unifies these states for you.useState
variables and conditionals for loading/error states.useLoadable
, useThen
, useAllThen
, etc.loading
, a LoadError
, or real data in one type.useEffect
, but with a focus on minimal boilerplate.Get rid of manual loading checks and experience simpler, more maintainable React apps. Give Loadable a try today!
No vulnerabilities found.
No security vulnerabilities found.