Repository: pacocoursey/use-delayed-render
Branch: master
Commit: 0d9dca16351f
Files: 5
Total size: 5.2 KB
Directory structure:
gitextract_2acjkg2m/
├── .gitignore
├── README.md
├── index.ts
├── package.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
dist
================================================
FILE: README.md
================================================
# useDelayedRender 
useDelayedRender is a react hook for delaying the render and unmount of a component. This is commonly used to animate UI on unmount.
## Installation
```
$ yarn add use-delayed-render
```
## Usage
Function signature:
```ts
const { mounted: boolean, rendered: boolean } = useDelayedRender(
active: boolean,
options?: {
enterDelay: number,
exitDelay: number,
onUnmount: () => void
}
)
```
Options:
- `active`: Whether your component is in an active state
- `enterDelay`: After mounting, the delay before `rendered` becomes true
- `exitDelay`: After `rendered` becomes false, the delay before unmounting
- `onUnmount`: A callback triggered after unmounting
Return values:
- `mounted`: Whether your component should be mounted in the DOM
- `rendered`: Whether your component should be visible
## Example
Render a modal, but delay the unmount so that our 2 second CSS transition completes before the modal is removed from the DOM.
```js
const Modal = ({ active }) => {
const { mounted, rendered } = useDelayedRender(active, {
exitDelay: 2000,
})
if (!mounted) return null
return (
{/* ... */}
)
}
```
This allows you to use simple CSS transitions to animate the mounting/unmounting of your component.
```css
.modal {
opacity: 0;
transition: opacity 2s ease;
}
.modal.visible {
opacity: 1;
}
```
## Why?
- Usually you would use [`react-transition-group`](https://github.com/reactjs/react-transition-group) to solve this, but the 2.37MB install size is a bit overkill, compared to this package at 491B gzipped.
```jsx
```
- Hooks solve the problem without needing a render function or HOC.
================================================
FILE: index.ts
================================================
import { useState, useRef, useCallback } from 'react'
interface Options {
enterDelay?: number
exitDelay?: number
onUnmount?: () => void
}
const useDelayedRender = (active: boolean = false, options: Options = {}) => {
const [, force] = useState()
const mounted = useRef(active)
const rendered = useRef(false)
const renderTimer = useRef(null)
const unmountTimer = useRef(null)
const prevActive = useRef(active)
const recalculate = useCallback(() => {
const { enterDelay = 1, exitDelay = 0 } = options
if (prevActive.current) {
// Mount immediately
mounted.current = true
if (unmountTimer.current) clearTimeout(unmountTimer.current)
if (enterDelay <= 0) {
// Render immediately
rendered.current = true
} else {
if (renderTimer.current) return
// Render after a delay
renderTimer.current = setTimeout(() => {
rendered.current = true
renderTimer.current = null
force({})
}, enterDelay)
}
} else {
// Immediately set to unrendered
rendered.current = false
if (exitDelay <= 0) {
mounted.current = false
} else {
if (unmountTimer.current) return
// Unmount after a delay
unmountTimer.current = setTimeout(() => {
mounted.current = false
unmountTimer.current = null
force({})
}, exitDelay)
}
}
}, [options])
// When the active prop changes, need to re-calculate
if (active !== prevActive.current) {
prevActive.current = active
// We want to do this synchronously with the render, not in an effect
// this way when active → true, mounted → true in the same pass
recalculate()
}
return {
mounted: mounted.current,
rendered: rendered.current
}
}
export default useDelayedRender
================================================
FILE: package.json
================================================
{
"name": "use-delayed-render",
"version": "0.1.0-beta.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"module": "dist/index.modern.js",
"source": "index.ts",
"license": "MIT",
"files": [
"dist"
],
"scripts": {
"prepublish": "yarn build",
"build": "microbundle --compress --no-sourcemap"
},
"devDependencies": {
"@types/react": "^16.9.35",
"microbundle": "^0.13.3",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": "*"
},
"dependencies": {},
"author": "@pacocoursey",
"repository": "pacocoursey/use-delayed-render"
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": ".",
"allowJs": true,
"jsx": "preserve",
"target": "esnext",
"module": "esnext",
"lib": ["dom", "es2019"],
"noEmit": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}