use-suspensible

A React hook that can make any data suspensible.


Keywords
react, hooks, suspense, react-hooks
License
MIT
Install
npm install use-suspensible@0.2.3

Documentation

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?

This is a proof-of-concept for using Suspense with non-promise based async libraries. Also see observable-hooks on implementing Render-as-You-Fetch pattern with Suspense and Rxjs Observable.

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>
  );
}