Skip to content

Commit

Permalink
feat(Delay, SuspensiveProvider): add new experimental features
Browse files Browse the repository at this point in the history
  • Loading branch information
manudeli committed Feb 5, 2023
1 parent 0f33051 commit cfd20e9
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 130 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage
Expand All @@ -19,8 +17,6 @@ build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
Expand Down
16 changes: 8 additions & 8 deletions packages/react-query/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@suspensive/react-query",
"version": "1.8.1",
"version": "1.9.2",
"keywords": [
"suspensive",
"react-query"
Expand Down Expand Up @@ -29,15 +29,15 @@
"scripts": {
"build": "rm -rf dist esm && tsc -p tsconfig.json --declaration --emitDeclarationOnly --declarationDir dist && rollup -c rollup.config.js",
"lint": "eslint .",
"npm:publish": "pnpm npm publish",
"npm:publish": "pnpm publish",
"prepack": "pnpm build",
"test": "echo \"Error: no test specified\" && exit 1",
"type:check": "tsc --noEmit"
},
"devDependencies": {
"@suspensive/babel": "*",
"@suspensive/eslint": "*",
"@suspensive/react": "^1.8.1",
"@suspensive/react": "1.9.2",
"@suspensive/rollup": "*",
"@suspensive/tsconfig": "*",
"@tanstack/react-query": "^4.16.1",
Expand All @@ -51,7 +51,7 @@
"typescript": "^4.9.3"
},
"peerDependencies": {
"@suspensive/react": "^1.8.1",
"@suspensive/react": "1.9.2",
"@tanstack/react-query": "^4.16.1",
"react": "^16.8 || ^17 || ^18"
},
Expand All @@ -65,9 +65,9 @@
},
"./package.json": "./package.json"
},
"import": "./esm/index.mjs",
"main": "./dist/index.js",
"module": "./esm/index.mjs",
"types": "./dist/index.d.ts"
"import": "esm/index.mjs",
"main": "dist/index.js",
"module": "esm/index.mjs",
"types": "dist/index.d.ts"
}
}
12 changes: 6 additions & 6 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@suspensive/react",
"version": "1.8.1",
"version": "1.9.2",
"keywords": [
"suspensive",
"react"
Expand Down Expand Up @@ -29,7 +29,7 @@
"scripts": {
"build": "rm -rf dist esm && tsc -p tsconfig.json --declaration --emitDeclarationOnly --declarationDir dist && rollup -c rollup.config.js",
"lint": "eslint .",
"npm:publish": "pnpm npm publish",
"npm:publish": "pnpm publish",
"prepack": "pnpm build",
"test": "echo \"Error: no test specified\" && exit 1",
"type:check": "tsc --noEmit"
Expand Down Expand Up @@ -61,9 +61,9 @@
},
"./package.json": "./package.json"
},
"import": "./esm/index.mjs",
"main": "./dist/index.js",
"module": "./esm/index.mjs",
"types": "./dist/index.d.ts"
"import": "esm/index.mjs",
"main": "dist/index.js",
"module": "esm/index.mjs",
"types": "dist/index.d.ts"
}
}
41 changes: 41 additions & 0 deletions packages/react/src/Delay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ComponentType, ReactNode, createContext, useContext, useEffect, useState } from 'react'
import { ComponentPropsWithoutChildren } from './types'

export const DelayContext = createContext<{ ms?: number }>({ ms: 0 })

type DelayProps = { ms?: number; children: ReactNode }
/**
* @experimental This is experimental feature.
*/
export const Delay = ({ ms, children }: DelayProps) => {
const delayMs = ms ?? useContext(DelayContext).ms ?? 0
const [isDelayed, setIsDelayed] = useState(delayMs === 0)

useEffect(() => {
const timerId = setTimeout(() => !isDelayed && setIsDelayed(true), delayMs)
return () => clearTimeout(timerId)
}, [delayMs])

return <>{isDelayed ? children : null}</>
}

/**
* @experimental This is experimental feature.
*/
export const withDelay = <Props extends Record<string, unknown> = Record<string, never>>(
Component: ComponentType<Props>,
delayProps?: ComponentPropsWithoutChildren<typeof Delay>
) => {
const Wrapped = (props: Props) => (
<Delay {...delayProps}>
<Component {...props} />
</Delay>
)

if (process.env.NODE_ENV !== 'production') {
const name = Component.displayName || Component.name || 'Component'
Wrapped.displayName = `withDelay(${name})`
}

return Wrapped
}
79 changes: 0 additions & 79 deletions packages/react/src/DelaySuspense.tsx

This file was deleted.

22 changes: 19 additions & 3 deletions packages/react/src/Suspense.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { Suspense as BaseSuspense, ComponentType, SuspenseProps } from 'react'
import { ComponentType, ReactNode, Suspense as ReactSuspense, SuspenseProps, createContext, useContext } from 'react'
import { useIsMounted } from './hooks'
import { ComponentPropsWithoutChildren } from './types'

const DefaultSuspense = (props: SuspenseProps) => <BaseSuspense {...props} />
export const SuspenseContext = createContext<{ fallback?: ReactNode }>({ fallback: undefined })
const useFallbackWithContext = (fallback: ReactNode) => {
const contextFallback = useContext(SuspenseContext).fallback

return fallback === null ? null : fallback ?? contextFallback
}

const DefaultSuspense = (props: SuspenseProps) => {
const fallback = useFallbackWithContext(props.fallback)

return <ReactSuspense {...props} fallback={fallback} />
}
if (process.env.NODE_ENV !== 'production') {
DefaultSuspense.displayName = 'Suspense'
}
const CSROnlySuspense = (props: SuspenseProps) => (useIsMounted() ? <BaseSuspense {...props} /> : <>{props.fallback}</>)
const CSROnlySuspense = (props: SuspenseProps) => {
const isMounted = useIsMounted()
const fallback = useFallbackWithContext(props.fallback)

return isMounted ? <ReactSuspense {...props} fallback={fallback} /> : <>{fallback}</>
}
if (process.env.NODE_ENV !== 'production') {
CSROnlySuspense.displayName = 'Suspense.CSROnly'
}
Expand Down
35 changes: 35 additions & 0 deletions packages/react/src/SuspensiveProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ContextType, ReactNode, useMemo } from 'react'
import { DelayContext } from './Delay'
import { SuspenseContext } from './Suspense'

type Configs = {
defaultOptions?: {
suspense?: ContextType<typeof SuspenseContext>
delay?: ContextType<typeof DelayContext>
}
}

/**
* @experimental This is experimental feature.
*/
export class SuspensiveConfigs {
public defaultOptions?: Configs['defaultOptions']

constructor(config: Configs = {}) {
this.defaultOptions = config.defaultOptions
}
}

/**
* @experimental This is experimental feature.
*/
export const SuspensiveProvider = ({ configs, children }: { configs: SuspensiveConfigs; children: ReactNode }) => {
const delayValue = useMemo(() => configs.defaultOptions?.delay || {}, [])
const suspenseValue = useMemo(() => configs.defaultOptions?.suspense || {}, [])

return (
<DelayContext.Provider value={delayValue}>
<SuspenseContext.Provider value={suspenseValue}>{children}</SuspenseContext.Provider>
</DelayContext.Provider>
)
}
3 changes: 2 additions & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export { AsyncBoundary, withAsyncBoundary } from './AsyncBoundary'
export { Suspense, withSuspense } from './Suspense'
export { ErrorBoundary, withErrorBoundary } from './ErrorBoundary'
export { ErrorBoundaryGroup, useErrorBoundaryGroup, withErrorBoundaryGroup } from './ErrorBoundaryGroup'
export { DelaySuspense, withDelaySuspense } from './DelaySuspense'
export { SuspensiveProvider, SuspensiveConfigs } from './SuspensiveProvider'
export { Delay, withDelay } from './Delay'
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions websites/visualization/components/forPlayground/api.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import axios from 'axios'

const delay = (ms: number = 1000) => new Promise((resolve) => setTimeout(resolve, ms))
const delayRandom = (maxMs: number = 1000) => delay(maxMs * Math.random())

export type Post = { id: number; title: string; body: string; userId: number }
export type Album = { id: number; title: string; userId: number }
export type Todo = { id: number; title: string; completed: boolean; userId: number }

export const posts = {
getMany: async () => {
await delay(Math.random() * 2000)
await delayRandom(3000)
return axios.get<Post[]>('https://jsonplaceholder.typicode.com/posts').then(({ data }) => data)
},
getOneBy: async ({ id }: { id: Post['id'] }) => {
await delay(Math.random() * 3000)
await delayRandom(3000)
return axios.get<Post>(`https://jsonplaceholder.typicode.com/posts/${id}`).then(({ data }) => data)
},
}

export const albums = {
getManyBy: async ({ userId }: { userId: number }) => {
await delay(Math.random() * 3000)
await delayRandom(3000)
return axios
.get<Album[]>(`https://jsonplaceholder.typicode.com/users/${userId}/albums`)
.then(({ data }) => data.splice(0, 2))
Expand All @@ -28,7 +29,7 @@ export const albums = {

export const todos = {
getManyBy: async ({ userId }: { userId: number }) => {
await delay(Math.random() * 3000)
await delayRandom(3000)
return axios
.get<Todo[]>(`https://jsonplaceholder.typicode.com/users/${userId}/todos`)
.then(({ data }) => data.splice(0, 2))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Suspense } from '@suspensive/react'
import { useSuspenseQuery } from '@suspensive/react-query'
import { albums, Post, posts, todos } from './api'
import { Spinner } from '../uis'
import { useIntersectionObserver } from './useIntersectionObserver'
import { useEffect, useRef, useState } from 'react'

Expand Down Expand Up @@ -32,7 +31,7 @@ const PostListItem = ({ post }: { post: Post }) => {
<li key={post.id} ref={ref} style={{ minHeight: 200 }}>
<h3>Title: {post.title}</h3>
{isShow && (
<Suspense.CSROnly fallback={<Spinner />}>
<Suspense.CSROnly>
<PostContent id={post.id} />
</Suspense.CSROnly>
)}
Expand Down
Loading

0 comments on commit cfd20e9

Please sign in to comment.