├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── licence ├── package.json ├── pnpm-lock.yaml ├── readme.md ├── src ├── index.d.ts └── index.tsx └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: {} 7 | 8 | jobs: 9 | test: 10 | name: Node.js v${{ matrix.node }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node: [16, 18, 20] 15 | steps: 16 | - uses: actions/checkout@main 17 | 18 | - name: (env) setup pnpm 19 | uses: pnpm/action-setup@master 20 | with: 21 | version: 8.6.5 22 | 23 | - name: (env) setup node v${{ matrix.node }} 24 | uses: actions/setup-node@main 25 | with: 26 | node-version: ${{ matrix.node }} 27 | cache: pnpm 28 | check-latest: true 29 | 30 | - run: pnpm install --ignore-scripts 31 | - run: pnpm run build 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *-lock.json 4 | *.lock 5 | *.log 6 | 7 | /coverage 8 | /.nyc_output 9 | 10 | # Editors 11 | *.iml 12 | /.idea 13 | /.vscode 14 | 15 | dist/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always", 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "trailingComma": "all", 6 | "useTabs": true, 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.md", 11 | "*.yml" 12 | ], 13 | "options": { 14 | "printWidth": 120, 15 | "tabWidth": 2, 16 | "useTabs": false 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Marais Rossouw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-boundary", 3 | "version": "0.1.1", 4 | "description": "A React async-boundary that couples an error-boundary as well as a suspense container", 5 | "keywords": [ 6 | "suspense", 7 | "react", 8 | "async boundary", 9 | "error boundary" 10 | ], 11 | "repository": "maraisr/async-boundary", 12 | "license": "MIT", 13 | "author": { 14 | "name": "Marais Rossouw", 15 | "email": "hi@marais.io", 16 | "url": "https://marais.io" 17 | }, 18 | "exports": { 19 | ".": { 20 | "import": "./dist/index.mjs", 21 | "require": "./dist/index.js" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "main": "dist/index.js", 26 | "module": "dist/index.mjs", 27 | "types": "types/index.d.ts", 28 | "files": [ 29 | "dist", 30 | "types" 31 | ], 32 | "scripts": { 33 | "build": "bundt", 34 | "format": "prettier \"{*,src/*,bench/*,.github/**/*}.+(tsx|js|yml|md)\" --write" 35 | }, 36 | "devDependencies": { 37 | "@marais/tsconfig": "0.0.2", 38 | "@types/react": "18.0.5", 39 | "bundt": "2.0.0-next.2", 40 | "prettier": "2.6.2", 41 | "react": "18.0.0", 42 | "typescript": "4.6.3" 43 | }, 44 | "peerDependencies": { 45 | "react": ">=16" 46 | }, 47 | "engines": { 48 | "node": ">=14" 49 | }, 50 | "volta": { 51 | "node": "16.14.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@marais/tsconfig': 9 | specifier: 0.0.2 10 | version: 0.0.2 11 | '@types/react': 12 | specifier: 18.0.5 13 | version: 18.0.5 14 | bundt: 15 | specifier: 2.0.0-next.2 16 | version: 2.0.0-next.2 17 | prettier: 18 | specifier: 2.6.2 19 | version: 2.6.2 20 | react: 21 | specifier: 18.0.0 22 | version: 18.0.0 23 | typescript: 24 | specifier: 4.6.3 25 | version: 4.6.3 26 | 27 | packages: 28 | 29 | /@marais/tsconfig@0.0.2: 30 | resolution: {integrity: sha512-xBWOcIbhm8t1ar63BluoL+DiKMHEIZ7Cc3pZ3FiKImUDmbNkqLmokQOIlAS/sB2SKc0yuAvmF5nKrkqOZsljZw==} 31 | dev: true 32 | 33 | /@types/prop-types@15.7.5: 34 | resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} 35 | dev: true 36 | 37 | /@types/react@18.0.5: 38 | resolution: {integrity: sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==} 39 | dependencies: 40 | '@types/prop-types': 15.7.5 41 | '@types/scheduler': 0.16.2 42 | csstype: 3.0.11 43 | dev: true 44 | 45 | /@types/scheduler@0.16.2: 46 | resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} 47 | dev: true 48 | 49 | /acorn@8.7.0: 50 | resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} 51 | engines: {node: '>=0.4.0'} 52 | hasBin: true 53 | dev: true 54 | 55 | /buffer-from@1.1.2: 56 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 57 | dev: true 58 | 59 | /bundt@2.0.0-next.2: 60 | resolution: {integrity: sha512-atcRbFchDUUdl382w9d2DhRQkvTiEEotmbtzhOXGw2PH02g2zQPkIJl7bCqq7fKifNNnESuqPfuQdMFBpUQA3Q==} 61 | engines: {node: '>=12'} 62 | hasBin: true 63 | dependencies: 64 | esbuild: 0.14.36 65 | rewrite-imports: 2.0.3 66 | terser: 5.12.1 67 | dev: true 68 | 69 | /commander@2.20.3: 70 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 71 | dev: true 72 | 73 | /csstype@3.0.11: 74 | resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==} 75 | dev: true 76 | 77 | /esbuild-android-64@0.14.36: 78 | resolution: {integrity: sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==} 79 | engines: {node: '>=12'} 80 | cpu: [x64] 81 | os: [android] 82 | requiresBuild: true 83 | dev: true 84 | optional: true 85 | 86 | /esbuild-android-arm64@0.14.36: 87 | resolution: {integrity: sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==} 88 | engines: {node: '>=12'} 89 | cpu: [arm64] 90 | os: [android] 91 | requiresBuild: true 92 | dev: true 93 | optional: true 94 | 95 | /esbuild-darwin-64@0.14.36: 96 | resolution: {integrity: sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==} 97 | engines: {node: '>=12'} 98 | cpu: [x64] 99 | os: [darwin] 100 | requiresBuild: true 101 | dev: true 102 | optional: true 103 | 104 | /esbuild-darwin-arm64@0.14.36: 105 | resolution: {integrity: sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==} 106 | engines: {node: '>=12'} 107 | cpu: [arm64] 108 | os: [darwin] 109 | requiresBuild: true 110 | dev: true 111 | optional: true 112 | 113 | /esbuild-freebsd-64@0.14.36: 114 | resolution: {integrity: sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==} 115 | engines: {node: '>=12'} 116 | cpu: [x64] 117 | os: [freebsd] 118 | requiresBuild: true 119 | dev: true 120 | optional: true 121 | 122 | /esbuild-freebsd-arm64@0.14.36: 123 | resolution: {integrity: sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==} 124 | engines: {node: '>=12'} 125 | cpu: [arm64] 126 | os: [freebsd] 127 | requiresBuild: true 128 | dev: true 129 | optional: true 130 | 131 | /esbuild-linux-32@0.14.36: 132 | resolution: {integrity: sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==} 133 | engines: {node: '>=12'} 134 | cpu: [ia32] 135 | os: [linux] 136 | requiresBuild: true 137 | dev: true 138 | optional: true 139 | 140 | /esbuild-linux-64@0.14.36: 141 | resolution: {integrity: sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==} 142 | engines: {node: '>=12'} 143 | cpu: [x64] 144 | os: [linux] 145 | requiresBuild: true 146 | dev: true 147 | optional: true 148 | 149 | /esbuild-linux-arm64@0.14.36: 150 | resolution: {integrity: sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==} 151 | engines: {node: '>=12'} 152 | cpu: [arm64] 153 | os: [linux] 154 | requiresBuild: true 155 | dev: true 156 | optional: true 157 | 158 | /esbuild-linux-arm@0.14.36: 159 | resolution: {integrity: sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==} 160 | engines: {node: '>=12'} 161 | cpu: [arm] 162 | os: [linux] 163 | requiresBuild: true 164 | dev: true 165 | optional: true 166 | 167 | /esbuild-linux-mips64le@0.14.36: 168 | resolution: {integrity: sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==} 169 | engines: {node: '>=12'} 170 | cpu: [mips64el] 171 | os: [linux] 172 | requiresBuild: true 173 | dev: true 174 | optional: true 175 | 176 | /esbuild-linux-ppc64le@0.14.36: 177 | resolution: {integrity: sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==} 178 | engines: {node: '>=12'} 179 | cpu: [ppc64] 180 | os: [linux] 181 | requiresBuild: true 182 | dev: true 183 | optional: true 184 | 185 | /esbuild-linux-riscv64@0.14.36: 186 | resolution: {integrity: sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==} 187 | engines: {node: '>=12'} 188 | cpu: [riscv64] 189 | os: [linux] 190 | requiresBuild: true 191 | dev: true 192 | optional: true 193 | 194 | /esbuild-linux-s390x@0.14.36: 195 | resolution: {integrity: sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==} 196 | engines: {node: '>=12'} 197 | cpu: [s390x] 198 | os: [linux] 199 | requiresBuild: true 200 | dev: true 201 | optional: true 202 | 203 | /esbuild-netbsd-64@0.14.36: 204 | resolution: {integrity: sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==} 205 | engines: {node: '>=12'} 206 | cpu: [x64] 207 | os: [netbsd] 208 | requiresBuild: true 209 | dev: true 210 | optional: true 211 | 212 | /esbuild-openbsd-64@0.14.36: 213 | resolution: {integrity: sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==} 214 | engines: {node: '>=12'} 215 | cpu: [x64] 216 | os: [openbsd] 217 | requiresBuild: true 218 | dev: true 219 | optional: true 220 | 221 | /esbuild-sunos-64@0.14.36: 222 | resolution: {integrity: sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==} 223 | engines: {node: '>=12'} 224 | cpu: [x64] 225 | os: [sunos] 226 | requiresBuild: true 227 | dev: true 228 | optional: true 229 | 230 | /esbuild-windows-32@0.14.36: 231 | resolution: {integrity: sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==} 232 | engines: {node: '>=12'} 233 | cpu: [ia32] 234 | os: [win32] 235 | requiresBuild: true 236 | dev: true 237 | optional: true 238 | 239 | /esbuild-windows-64@0.14.36: 240 | resolution: {integrity: sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==} 241 | engines: {node: '>=12'} 242 | cpu: [x64] 243 | os: [win32] 244 | requiresBuild: true 245 | dev: true 246 | optional: true 247 | 248 | /esbuild-windows-arm64@0.14.36: 249 | resolution: {integrity: sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==} 250 | engines: {node: '>=12'} 251 | cpu: [arm64] 252 | os: [win32] 253 | requiresBuild: true 254 | dev: true 255 | optional: true 256 | 257 | /esbuild@0.14.36: 258 | resolution: {integrity: sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==} 259 | engines: {node: '>=12'} 260 | hasBin: true 261 | requiresBuild: true 262 | optionalDependencies: 263 | esbuild-android-64: 0.14.36 264 | esbuild-android-arm64: 0.14.36 265 | esbuild-darwin-64: 0.14.36 266 | esbuild-darwin-arm64: 0.14.36 267 | esbuild-freebsd-64: 0.14.36 268 | esbuild-freebsd-arm64: 0.14.36 269 | esbuild-linux-32: 0.14.36 270 | esbuild-linux-64: 0.14.36 271 | esbuild-linux-arm: 0.14.36 272 | esbuild-linux-arm64: 0.14.36 273 | esbuild-linux-mips64le: 0.14.36 274 | esbuild-linux-ppc64le: 0.14.36 275 | esbuild-linux-riscv64: 0.14.36 276 | esbuild-linux-s390x: 0.14.36 277 | esbuild-netbsd-64: 0.14.36 278 | esbuild-openbsd-64: 0.14.36 279 | esbuild-sunos-64: 0.14.36 280 | esbuild-windows-32: 0.14.36 281 | esbuild-windows-64: 0.14.36 282 | esbuild-windows-arm64: 0.14.36 283 | dev: true 284 | 285 | /js-tokens@4.0.0: 286 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 287 | dev: true 288 | 289 | /loose-envify@1.4.0: 290 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 291 | hasBin: true 292 | dependencies: 293 | js-tokens: 4.0.0 294 | dev: true 295 | 296 | /prettier@2.6.2: 297 | resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==} 298 | engines: {node: '>=10.13.0'} 299 | hasBin: true 300 | dev: true 301 | 302 | /react@18.0.0: 303 | resolution: {integrity: sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==} 304 | engines: {node: '>=0.10.0'} 305 | dependencies: 306 | loose-envify: 1.4.0 307 | dev: true 308 | 309 | /rewrite-imports@2.0.3: 310 | resolution: {integrity: sha512-R7ICJEeP3y+d/q4C8YEJj9nRP0JyiSqG07uc0oQh8JvAe706dDFVL95GBZYCjADqmhArZWWjfM/5EcmVu4/B+g==} 311 | engines: {node: '>=6'} 312 | dev: true 313 | 314 | /source-map-support@0.5.21: 315 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 316 | dependencies: 317 | buffer-from: 1.1.2 318 | source-map: 0.6.1 319 | dev: true 320 | 321 | /source-map@0.6.1: 322 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 323 | engines: {node: '>=0.10.0'} 324 | dev: true 325 | 326 | /source-map@0.7.3: 327 | resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} 328 | engines: {node: '>= 8'} 329 | dev: true 330 | 331 | /terser@5.12.1: 332 | resolution: {integrity: sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==} 333 | engines: {node: '>=10'} 334 | hasBin: true 335 | dependencies: 336 | acorn: 8.7.0 337 | commander: 2.20.3 338 | source-map: 0.7.3 339 | source-map-support: 0.5.21 340 | dev: true 341 | 342 | /typescript@4.6.3: 343 | resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} 344 | engines: {node: '>=4.2.0'} 345 | hasBin: true 346 | dev: true 347 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # `` 2 | 3 | [![CI](https://github.com/maraisr/async-boundary/actions/workflows/ci.yml/badge.svg)](https://github.com/maraisr/async-boundary/actions/workflows/ci.yml) [![licenses](https://licenses.dev/b/npm/async-boundary?style=dark)](https://licenses.dev/npm/async-boundary) 4 | 5 | > A React async-boundary that couples an error-boundary as well as a suspense container 6 | 7 | ## ⚙️ Install 8 | 9 | ```sh 10 | yarn add async-boundary 11 | ``` 12 | 13 | ## 🚀 Usage 14 | 15 | ```tsx 16 | import { AsyncBoundary } from 'async-boundary'; 17 | 18 | const SuspensfulContainer = () => { 19 | return ( 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | const SlowSuspensfulComponent = () => { 27 | const data = resource.data.read(); 28 | 29 | return

{data.thing}

; 30 | }; 31 | ``` 32 | 33 | > Please read more about suspense at 34 | > [Concurrent React (Suspense for Data Fetching)](https://reactjs.org/docs/concurrent-mode-suspense.html) 35 | 36 | ## 🔎 API 37 | 38 | ### AsyncBoundary: FunctionComponent 39 | 40 | #### Props 41 | 42 | - `fallback`: The `ReactChild` to render whilst Suspending 43 | - `errorFallback`: The `ReactChild` to render when an error occurred. 44 | - Has a `retryFn` callback passed to it, calling it will remount the `` children. 45 | - `onError`: The `componentDidCatch` callback. 46 | 47 | ## License 48 | 49 | MIT © [Marais Rossouw](https://marais.io) 50 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentLifecycle, ReactNode, FunctionComponent, ElementType } from "react"; 2 | 3 | export type ErrorFallbackComponentType = ElementType<{ 4 | retryFn?(): void; 5 | error: Error | null; 6 | }>; 7 | 8 | interface Props { 9 | fallback?: ReactNode; 10 | onError?: ComponentLifecycle['componentDidCatch']; 11 | errorFallback?: ErrorFallbackComponentType; 12 | 13 | children?: ReactNode; 14 | } 15 | 16 | export type AsyncBoundaryComponent = FunctionComponent; 17 | 18 | /** 19 | * A component that wraps an ErrorBoundary along with a Suspense boundary. Akin to; 20 | * 21 | * ```jsx 22 | * 23 | * 24 | * your thing 25 | * 26 | * 27 | * ``` 28 | * 29 | * @example 30 | * 31 | * ```jsx 32 | * const ErrorFallback = ({error, retryFn}) => ( 33 | *
34 | *
{error.message}
35 | * 36 | *
37 | * ); 38 | * 39 | * const track = (error) => telemetry.captureException(error); 40 | * 41 | * 45 | * ... 46 | * 47 | * ``` 48 | */ 49 | export const AsyncBoundary: FunctionComponent; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Suspense } from 'react'; 2 | import type { ComponentProps, ReactNode } from 'react'; 3 | 4 | import type { 5 | AsyncBoundaryComponent, 6 | ErrorFallbackComponentType, 7 | } from 'async-boundary'; 8 | 9 | type Props = { 10 | fallback?: ErrorFallbackComponentType; 11 | onError?: ComponentProps['onError']; 12 | children?: ReactNode; 13 | }; 14 | type State = { error: Error | null }; 15 | 16 | const DefaultErrorFallbackComponent: ErrorFallbackComponentType = ({ 17 | retryFn, 18 | error, 19 | }) => ( 20 |

21 | An error has occurred, to 22 | try again. 23 | {error ?

{error.message}
: null} 24 |

25 | ); 26 | 27 | class ErrorBoundary extends Component { 28 | state = { 29 | error: null, 30 | }; 31 | 32 | static getDerivedStateFromError(error: Error) { 33 | return { error: error ?? null }; 34 | } 35 | 36 | componentDidCatch(error: Error, errorInfo: unknown) { 37 | this.props.onError?.(error, errorInfo); 38 | } 39 | 40 | retryFn = () => { 41 | this.setState({ error: null }); 42 | }; 43 | 44 | render() { 45 | const { children, fallback } = this.props; 46 | 47 | const Fallback = fallback ?? DefaultErrorFallbackComponent; 48 | 49 | return this.state.error ? ( 50 | 51 | ) : ( 52 | children 53 | ); 54 | } 55 | } 56 | 57 | const DefaultFallback = loading...; 58 | 59 | export const AsyncBoundary: AsyncBoundaryComponent = ({ 60 | children, 61 | onError, 62 | fallback = DefaultFallback, 63 | errorFallback, 64 | }) => ( 65 | 66 | {children} 67 | 68 | ); 69 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@marais/tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "esnext", 6 | "jsx": "react-jsx", 7 | "lib": ["esnext", "dom"], 8 | "paths": { 9 | "async-boundary": ["./src/index.d.ts"] 10 | } 11 | }, 12 | "include": [ "src/*.d.ts", "src"], 13 | "exclude": ["node_modules"] 14 | } 15 | --------------------------------------------------------------------------------