Skip to content
React hooks that can make any data suspensible.
JavaScript TypeScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode
assets
src
test
.editorconfig
.eslintrc.js
.gitignore
.prettierrc
.travis.yml
CHANGELOG.md
LICENSE
README.md
commitlint.config.js
jest.config.js
package.json
tsconfig.eslint.json
tsconfig.json
yarn.lock

README.md

use-suspensible

npm-version Build Status Coverage Status

Commitizen friendly Conventional Commits JavaScript Style Guide code style: prettier

use-suspensible

React hooks that can make any data suspensible.

Why?

If you follow the Relay Suspense pattern you need to add wrappers to async logic then use read() to get data in Components.

This means:

  • It can only apply to Promise based async logic.
  • You have to write logic and use data in a specific way.
  • When you successfully read() a piece of data, it means the data is "fetched" but not necessary "usable"(unless you write business logic in a conventional way to make sure they are the same). You still need to add logic to check e.g. variation/validation of the data.

use-suspensible does not care how you implement the business logic. It only cares about if the data is usable or not. This makes it a universal solution for Suspense.

With use-suspensible you simply fetch and use data as usual/you like. It just works (with almost no cost).

Installation

yarn

yarn add use-suspensible

npm

npm install --save use-suspensible

Usage

import React, { Suspense, useState, useEffect } from 'react'
import { useSuspensible } from 'use-suspensible'

function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ content: "content" })
    }, 3000)
  })
}

const App = () => {
  const [data, setData] = useState()
  useEffect(
    () => {
      fetchData().then(setData)
    },
    []
  )

  return (
    <Suspense fallback={<h1>Loading...</h1>}>
      <Content data={data} />
    </Suspense>
  )
}

function Content({ data }) {
  useSuspensible(data)
  return (
    <h1>{data.content}</h1>
  );
}

Default trigger Suspense on null or undefined.

useSuspensible(data)

Custom comparison for checking finish state.

useSuspensible(data, data => data.status === 'finish')

You can have any number of useSuspensible in a Component.

useSuspensible(data1)
useSuspensible(data2)
useSuspensible(data3, data => data.status === 'finish')

return (
  <>
    <h1>{data1.content}</h1>
    <h1>{data2.content}</h1>
    <h1>{data3.content}</h1>
  </>
)

TypeScript >= 3.7

interface StatePending {
  status: 'pending'
  value: null
}

interface StateFinish {
  status: 'finish'
  value: number
}

type States = StatePending | StateFinish

//....

useSuspensible(
  data,
  (data: States): data is StateFinish => data.status === 'finish'
)

// Now data is of `StateFinish` type.

Beware

Due to the design of Suspense, each time a suspender is thrown, the children of Suspense Component will be destroyed and reloaded. Do not initialize async data and trigger Suspense in the same Component.

import React, { Suspense, useState, useEffect } from 'react'
import { useSuspensible } from 'use-suspensible'

function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ content: "content" })
    }, 3000)
  })
}

const App = () => {
  return (
    <Suspense fallback={<h1>Loading...</h1>}>
      <Content />
    </Suspense>
  )
}

function Content({ data }) {
  const [data, setData] = useState()
  // This will cause infinite update.
  useEffect(
    () => {
      fetchData().then(setData)
    },
    []
  )
  useSuspensible(data)
  return (
    <h1>{data.content}</h1>
  );
}
You can’t perform that action at this time.