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 ![npm bundle size](https://img.shields.io/bundlephobia/minzip/use-delayed-render) 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 } }