The simplest React data (re)fetching library ever made

Overview

hazae41's xswr

The simplest React data (re)fetching library ever made

npm i @hazae41/xswr

Go to the docs

Philosophy

XSWR is very inspired from SWR (which stands for "Stale While Revalidate").

In fact, it's a side project I made to fill in the gaps of SWR, that ended up production-ready.

XSWR uses two new approaches compared to other data fetching libraries like swr or react-query:

  1. Encapsulating key, fetcher and resource type in a single abstraction called "handle".
  2. Composing features with very simple hooks instead of having bloated configuration patterns and unexpected behaviours.

By using these two approaches, XSWR aims to help you highly centralize and reuse things.

Comparison with swr and react-query

Features

Current features

  • 100% TypeScript
  • Composition-based hooks
  • Very easy learning curve
  • No dependency except React
  • Not over-engineered (hello react-query)
  • No unexpected behaviour (hello swr)
  • Backend agnostic fetching (REST, GraphQL, WebSocket)
  • Storage agnostic caching (new Map(), LocalStorage, IndexedDB)
  • Request deduplication
  • Exponential backoff retry
  • Cursor-based pagination
  • Automatic refetching
  • Dependent queries
  • SSR & ISR support
  • Optimistic mutations
  • Cancellable requests
  • Automatic cancellation
  • Automatic garbage collection
  • Per-query persistence

Upcoming features

  • Transport agnostic streaming (ethers.js, WebSockets, Socket.io)
  • Store normalization
  • Bidirectional scrolling
  • React suspense

Preparing your app

You just have to wrap your app in a XSWR.CoreProvider component.

function MyWrapper() {
  return <XSWR.CoreProvider>
    <MyAwesomeApp />
  </XSWR.CoreProvider>
}

You can also partition your app using multiple providers and storages.

Your first sandwich

When using xswr and its composition-based hooks, you create a sandwich and only include the ingredients you want.

This shows a simple and complete way of doing a request on /api/data using JSON, display it with a loading, and automatically refetch it.

Create a fetcher for your request

async function fetchAsJson<T>(url: string) {
  const res = await fetch(url)
  const data = await res.json() as T
  return { data }
}

Then create your hook using useSingle (or useScroll) and some other hooks you like

function useMyData() {
  const handle = XSWR.useSingle<MyData>(`/api/data`, fetchAsJson)
  
  XSWR.useFetch(handle) // Fetch on mount and on url change
  XSWR.useVisible(handle) // Fetch when the page becomes visible
  XSWR.useOnline(handle) // Fetch when the browser becomes online
  return handle
}

Now you can use it in your component

function MyApp() {
  const { data, error } = useMyData()

  if (error)
    return <MyError error={error} />
  if (!data)
    return <MyLoading />
  return <MyPage data={data} />
}

Go to the docs

You might also like...
A Fetch Library Support React New Suspense SSR

use-suspense-fetch A data fetching library for React Suspense. inspired by use-asset Feature use LRU Cache support create custom cache support React 1

React-Native library for the WidgetKit framework. Integrate a Widget into your App πŸπŸ“±
React-Native library for the WidgetKit framework. Integrate a Widget into your App πŸπŸ“±

react-native-widgetkit React-Native Library for the iOS 🍏 WidgetKit framework Table of Contents πŸ“š Introduction πŸ‘¨β€πŸ« Installation πŸ–‡β€ Usage πŸ‘¨πŸ»β€πŸ’»

Logger utility for Teaful State Management library for react/preact.

A simple and minimal logger for Teaful state management library.

React library to send nodes into a node at a different place in the component tree.

Unopinionated React library to render content into another place of the React tree (without losing the React context). This is especially useful for modals or popovers.

A helper library to use react-query more efficient, consistency

A helper library to use react-query more efficient, consistency

Fine-grained atomic React state management library

particule Fine-grained atomic React state management library yarn add particule ✨ Features πŸš€ Examples Basic Fine-grained Composition Suspense Custom

A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨
A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨

React Spectrum A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨 Inspired by this code illustration on CodeSandbox hom

React library for interacting with the Aptos blockchain.

react-aptos React library for interacting with the Aptos blockchain.

Kyōka is a minimalistic state management library, designed for React

Kyōka Kyoka is a minimalistic state management library, designed for React. Features No boilerplate Zero dependency Straightforward Lightweight Object

Comments
  • React Native support

    React Native support

    I see that the only dependency is React, therefore this library should be compatible with React Native.

    Just wanted to confirm this is the case -- if so, could you add this information to the README?

    opened by ansh 5
Releases(1.1.6)
  • 1.1.6(Nov 3, 2022)

  • 1.1.5(Nov 3, 2022)

  • 1.1.2(Oct 26, 2022)

  • 1.1.0(Oct 26, 2022)

    BREAKING CHANGES

    • Renamed "Handle" to "Query"
    • Renamed "Object" to "Instance"
    • Renamed "use" to "useQuery"
    • Renamed "single" to "getSingleSchema"
    • Renamed "scroll" to "getScrollSchema"
    • Renamed "useSingle" to "useSingleQuery"
    • Renamed "useScroll" to "useScrollQuery"
    • XSWR. is no longer needed (but still works)

    Schemas become:

    function getHelloSchema() {
      return getSingleSchema<Hello>("/api/hello", fetchAsJson)
    }
    

    Mixtures become:

    function useAutoFetchMixture(query: Query) {
      useFetch(query)
      useVisible(query)
      useOnline(query)
    }
    

    Mixes become:

    function useHelloMix() {
      const query = useQuery(getHelloSchema, [])
      useAutoFetchMixture(query)
      return query
    }
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.95(Oct 11, 2022)

    NO BREAKING CHANGES

    CHANGES

    • Removed bunchee in favor of rollup => less bugs
    • No longer bundled/minified => easier debugging
    • Added source maps => easier debugging
    • Moved tslib as peerDependency => reduced bundle size
    Source code(tar.gz)
    Source code(zip)
  • 1.0.89(Oct 10, 2022)

  • 1.0.86(Oct 5, 2022)

    CHANGES

    • Updaters now yield Result instead of State: only allows data, error, and times (e.g. can't modify aborter in yield)
    • Added new member realData in States, Handles and Objects: it allows you to get the real, non-optimistic data
    Source code(tar.gz)
    Source code(zip)
  • 1.0.85(Oct 5, 2022)

  • 1.0.81(Sep 29, 2022)

    CHANGES

    • Update generator now can yield multiple optimistics:
    user.update(async function* () {
      yield {Β data: "Naive optimistic data" } // data that we create on-the-fly from what we suppose it will look like
      const tx = await contract.doSomething(user.data) // make an Ethereum transaction
      yield { data: getUserDataFromLogs(tx) } // data that we expect before the transaction is mined
      const txr = await tx.wait() // wait for transaction mined
    }, { timeout: 60 * 1000 })
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.80(Sep 26, 2022)

    CHANGES

    • You can now return nothing in your updater in order to use the regular fetcher:
    document.update(async function* () {
      yield { data: "My optimistic document" }
      await new Promise(ok => setTimeout(ok, 5000))
      // no return, will use the fetcher to get the new state
    })
    

    This is useful when you know the resource will be updated but want to display an optimistic state. Also, { cache: "reload" } will be passed to the fetcher in order to skip the cache, feel free to pass it to JS fetch or Axios or ignore it

    Source code(tar.gz)
    Source code(zip)
  • 1.0.77(Sep 23, 2022)

    BREAKING CHANGES

    • Mutations are now locked while an optimistic update is ongoing
    • You can pass custom timeout, expiration, cooldown to update: update(updater, params, aborter)
    await document.update(async function* (previous) {
      yield { data: "My optimistic document" }
      return await postAsJson("/api/edit", "My updated document")
    }, { timeout: 60 * 1000 })
    

    CHANGES

    • Better error logging when aborted or timed out
    Source code(tar.gz)
    Source code(zip)
  • 1.0.74(Sep 22, 2022)

    BREAKING CHANGES

    • New optimistic API with more control, just use "yield" with your optimistic state, which will be replaced if success or reverted if error
    document.update(async function* (previous) {
      yield { data: "My optimistic document" }
      return await postAsJson("/api/edit", "My document")
    })
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.73(Sep 20, 2022)

    BREAKING CHANGES

    • Removed N type parameter, just use Data | Ref in your first type parameter
    BEFORE: XSWR.single<Data, Error, Ref>(...)
    
    NOW: XSWR.single<Data | Ref, Error>(...)
    
    • Removed ParamsContext, for avoiding nasty bugs when you use a schema that's in another branch of the params tree. You can still use params in CoreContext for global params
    • Removed Normal in favor of a new normalization pattern with more control and more type safety
    async function getDataRef(data: Data | Ref, more: XSWR.NormalizerMore) {
      if ("ref" in data) return data
      const schema = getDataSchema(data.id)
      await schema.normalize(data, more)
      return { ref: true, id: data.id } as Ref
    }
    

    See the docs for more details

    CHANGES

    • Fixed comparison bug when calling first() on a normalized scrolling resource
    Source code(tar.gz)
    Source code(zip)
  • 1.0.71(Sep 12, 2022)

    BREAKING CHANGES

    • fetch, refetch, first and scroll now throw on SSR

    CHANGES

    • Added handle.suspend() a super natural and easy way to suspend your components
    function Component() {
      const { data, error, suspend } = useData()
    
      // Throw the error
      if (error) throw error
    
      // Fetch and suspend until next state change
      if (!data) throw suspend()
    
      return <div>{JSON.stringify(data)}</div>
    }
    

    That's it, you have control over when you suspend and when your throw πŸš€

    • Fixed bugs and simplified code
    Source code(tar.gz)
    Source code(zip)
  • 1.0.69(Sep 8, 2022)

    BREAKING CHANGES

    • Scroll resources now need to have a Normalized type that extends the Data type To put it another way, your Data type must have an union with your data and your normalized data:
    return XSWR.scroll<Data | string, Error, string>(url, fetcher)
    

    This is because on scroll, previous pages are in normalized form, but the next page is in data form

    newData = [...previousPages: Normalized[], nextPage: Data]
    

    CHANGES

    • Object states are now lazy initialized on fetch(), mutate(), refetch(), scroll(), update()
    • Fixed bugs
    • Fixed types
    Source code(tar.gz)
    Source code(zip)
  • 1.0.64(Sep 8, 2022)

    BREAKING CHANGES

    • make(), Schema.make() and Object constructors no longer need init parameter, they will get data only once!
    • mutate() now accepts a function
    mutate((currentState?: State) => State | undefined)
    

    CHANGES

    • Improved handles synchronization, no longer double gets
    • Handles no longer require a memoized fetcher and static params
    • Handle and object methods will now wait for state initialization, no more need for ready check
    • Handles and schemas now accept an undefined fetcher; fetch() and refetch() will just return the current state
    • Simplified state mutation mechanism, 0% weird behaviour
    • Added a test for complex and nested store normalization
    Source code(tar.gz)
    Source code(zip)
  • 1.0.60(Sep 6, 2022)

    BREAKING CHANGES

    • All type parameters now follow the order D,E,N,K; some types may break if you used type parameters before

    CHANGES

    • Added store normalization, out of the box, no dependency needed

    Example

    We'll use normalization for an array that contains items of type Data, each with an unique id

    interface Data {
      id: string
      name: string
    }
    

    First, create a schema factory for an item

    function getDataSchema(id: string) {
      return XSWR.single<Data>(`/api/data?id=${id}`, fetchAsJson)
    }
    

    Then, create a normal for an item

    A normal is an object that encapsulates your data, its schema, and a reference to your data (so we can delete the original data and just keep the reference)

    function getDataNormal(data: Data) {
      return new XSWR.Normal(data, getDataSchema(data.id), data.id)
    }
    

    Then, create a schema for your container, and create a normalizer, it will return then new structure of your container

    In this case, all the array is mapped to normals, which will then automatically be replaced by references by XSWR

    function getAllDataSchema() {
      function normalizer(data: Data[]) {
        return data.map(getDataNormal)
      }
    
      return XSWR.single<Data[], Error, string[]>(
        `/api/data/all`,
        fetchAsJson,
        { normalizer })
    }
    

    Notice the extra type parameter string[], it's the final type of our container, after normalization

    That's it! No dependency needed, it just works!

    You can find a full example in test/next/normalizer

    Source code(tar.gz)
    Source code(zip)
  • 1.0.59(Sep 5, 2022)

    NO BREAKING CHANGES

    CHANGES

    • make, Schema.make, and Object constructors now accept initialize as last optional parameter (default true) in order to initialize the object with the current state, it can be set to false for optimizing performances when you just need to call mutate and don't need the data, for example
    const object = make(getHelloSchema(), false) // Won't load the state in the object
    await object.mutate({ data: "Hello World" })
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.58(Sep 5, 2022)

    BREAKING CHANGES

    • AsyncLocalStorage and SyncLocalStorage (along with their hooks) now accept a prefix as first parameter
    useAsyncLocalStorage("mycache")
    

    Default prefix is xswr:

    CHANGES

    • Storages are now doing garbage collection out of the box! ⚑️
    • Added garbage collection on unmount and on page unload for SyncLocalStorage and AsyncLocalStorage
    • Added garbage collection on unmount and on page load for IDBStorage
    • Added collect() and unmount() methods on all storages
    Source code(tar.gz)
    Source code(zip)
  • 1.0.57(Sep 4, 2022)

    BREAKING CHANGES

    • Renamed jsoneq to jsonEquals
    • Catched errors are no longer deduplicated or expired

    CHANGES

    • You can now return a custom time in your fetcher
    async function fetchAsJson<T>(url: string, more: XSWR.PosterMore<T>) {
      const { signal } = more
    
      const res = await fetch(url, { signal })
      const cooldown = Date.now() + (5 * 1000)
      const expiration = Date.now() + (10 * 1000)
    
      if (!res.ok) {
        const error = new Error(await res.text())
        return { error, cooldown, expiration } // implicit Date.now()
      }
    
      const data = await res.json() as T
      return { data, time: data.time, cooldown, expiration } // explicit date.time (milliseconds)
    }
    
    • Fixed bugs
    Source code(tar.gz)
    Source code(zip)
  • 1.0.56(Sep 3, 2022)

    NO BREAKING CHANGES

    CHANGES

    • You can now return an error in your fetcher instead of throwing
    async function fetchAsJson<T>(url: string, more: XSWR.PosterMore<T>) {
      const { signal } = more
    
      const res = await fetch(url, { signal })
      const cooldown = Date.now() + (5 * 1000)
      const expiration = Date.now() + (10 * 1000)
    
      if (!res.ok) {
        const error = new Error(await res.text())
        return { error, cooldown, expiration }
      }
    
      const data = await res.json() as T
      return { data, cooldown, expiration }
    }
    

    This way you can still set expiration and cooldown

    • Added scroll tests
    • New mutation merge strategy (not breaking)
    • Bug fixes and performances improvements
    Source code(tar.gz)
    Source code(zip)
  • 1.0.55(Sep 3, 2022)

    BREAKING CHANGES

    • refetch() now cancels ongoing requests (unless it's an optimistic update)
    • update() no longer throws on error, it just mutates the state with the error (you can still check for error by checking the returned state)

    CHANGES

    • handle.aborter is now available on optimistic updates
    • handle.optimistic is now available to tell whether the ongoing request is optimistic
    • Lot of bug fixes and behaviour fixes
    Source code(tar.gz)
    Source code(zip)
  • 1.0.52(Aug 31, 2022)

    NO BREAKING CHANGE

    CHANGES

    • You can now use IndexedDB storage πŸš€
    const storage = new XSWR.IDBStorage("mydb")
    
    function getHelloSchema() { // See the docs for the new schema pattern
      return XSWR.single("/api/hello", fetchAsJson, { storage })
    }
    
    • Improved performances
    Source code(tar.gz)
    Source code(zip)
  • 1.0.51(Aug 30, 2022)

    BREAKING CHANGES

    • New syntax for XSWR.use: XSWR.use(schema) => XSWR.use(factory, deps)
    function getCatShema(id: string) {
      return XSWR.single(["/api/cat", id], fetchAsJson)
    }
    
    function useCatBase(id: string) {
     return XSWR.use(getCatSchema, [id])
    }
    

    It works like useMemo but the deps are passed to the factory.

    Source code(tar.gz)
    Source code(zip)
  • 1.0.49(Aug 29, 2022)

    NO BREAKING CHANGES

    CHANGES

    • You can now use your favorite resources without using a hook, thanks to schemas and objects:
    // Create a schema for your resource
    function getHelloSchema(id: string) {
      return XSWR.single("/api/hello", fetchAsJson)
    }
    
    // Use it in a hook
    function useHello(id: string) {
      const handle = XSWR.use(getHelloSchema(id))
    
      XSWR.useFetch(handle)
      return handle
    }
    
    // Or in a function
    function Setter() {
      const { make } = XSWR.useXSWR()
    
      const set = useCallback(async () => {
        const object = make(getHelloSchema("123"))
        await object.mutate({ data: "hello world" })
        await object.refetch()
      }, [make])
    
      return <button onClick={set}>
        Click me
      </button>
    }
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.45(Aug 29, 2022)

    NO BREAKING CHANGES

    CHANGES

    • You can now use XSWR.ParamsProvider to provide custom params to all children without changing the core provider
    export default function Wrapper() {
      return <XSWR.ParamsProvider
        serializer={GZIP}>
        <Page />
      </XSWR.ParamsProvider>
    }
    

    Since params = { ...parent, ...current } the downmost params will override. If I then use a custom serializer in my handle, JSON will be used instead of GZIP.

    useSingle(url, fetcher, { serializer: JSON })
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.42(Aug 28, 2022)

    NO BREAKING CHANGES

    CHANGES

    • You can now use a custom storage in handle factories
    // Create a storage in the hook or at top-level
    const storage = XSWR.useAsyncLocalStorage()
    
    const handle = XSWR.useSingle(
      "/api/hello",
      fetchAsJson,
     { storage })
    
    XSWR.useFetch(handle)
    return handle
    
    • You can now use a custom key serializer in handle factories
    const handle = XSWR.useSingle(
      "/api/hello",
      fetchAsJson,
     { serializer: GZIP })
    
    XSWR.useFetch(handle)
    return handle
    
    • You can now use a custom equals function in handle factories
    const handle = XSWR.useSingle(
      "/api/hello",
      fetchAsJson,
     { equals: deepCompare })
    
    XSWR.useFetch(handle)
    return handle
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.40(Aug 28, 2022)

    NO BREAKING CHANGES

    CHANGES

    • Allow custom serializer in Core, for serializing arbitrary keys
    • Allow custom serializer in LocalStorages, for serializing states
    • Default serializer is JSON, i added GZIP example in tests
    Source code(tar.gz)
    Source code(zip)
  • 1.0.39(Aug 28, 2022)

    NO BREAKING CHANGES

    CHANGES

    • Split cache and storage
    • Added support for async storages
    • Added AsyncLocalStorage (SSR compatible) and SyncLocalStorage (no SSR)
    • Fixed bugs on core unmount
    • Improved performances by avoiding multiple get()
    • Added tests for localStorage
    Source code(tar.gz)
    Source code(zip)
Owner
Haz Γ† 41
cypherpunk
Haz Γ† 41
React-compress - This compress library was made with Brotli and Gzip help, for React users who want to make website more performance and reduce JS bundle code

React-compress - This compress library was made with Brotli and Gzip help, for React users who want to make website more performance and reduce JS bundle code

Koma Human 26 Oct 28, 2022
Extended utils for βš›οΈ React.Children data structure that adds recursive filter, map and more methods to iterate nested children.

React Children Utilities Recursive and extended utils for React children opaque data structure. Installation Available as a package and can be added t

Fernando Pasik 269 Nov 17, 2022
Collection of Google fonts as typeface data for usage with three.js, react-three-fiber, and other tools.

Collection of Google fonts as typeface data for usage with three.js, react-three-fiber, and other tools.

Components AI 59 Oct 9, 2022
Superjson-remix: A solution for Remix that allows you to send binary data from your loader function to your React client app

superjson-remix A solution for Remix that allows you to send binary data from your loader function to your React client app. It uses the awesome super

Donavon West 69 Dec 2, 2022
baseservice is a class that contains all methods with or without auth. just create the instance and pass the data to the endpoint.

baseservice is a class that contains all methods with or without auth. just create the instance and pass the data to the endpoint.

Francisco Marmolejo Martinez 1 Apr 6, 2022
React code splitting made easy. Reduce your bundle size without stress βœ‚οΈ ✨ .

React code splitting made easy. Reduce your bundle size without stress βœ‚οΈ ✨ . npm install @loadable/component Docs See the documentation at loadable-c

Greg BergΓ© 6.9k Dec 1, 2022
A lightweight react library that converts raw HTML to a React DOM structure.

A lightweight react library that converts raw HTML to a React DOM structure.

Arve Knudsen 718 Dec 4, 2022
A react or react native library to call functions comparing the last time that it was called

A react or react native library to call functions comparing the last time that it was called and running it when it's really needed. Avoiding unnecessary database calls or data loads that are updated at a certain time.

Hubert Ryan 6 Nov 9, 2022
πŸ‘‰ React Finger is a library of gesture events for React that allows developers to use a single set of events for both desktop and mobile devices.

?? React Finger is a library of gesture events for React that allows developers to use a single set of events for both desktop and mobile devices.

Houfeng 7 Nov 3, 2022
The next generation state management library for React

The next generation state management library for React

Bytedance Inc. 159 Nov 2, 2022