Skip to content

Commit 4639e17

Browse files
committed
Initial commit
0 parents  commit 4639e17

File tree

15 files changed

+4686
-0
lines changed

15 files changed

+4686
-0
lines changed

.eslintrc.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = {
2+
"env": {
3+
"browser": true,
4+
"es2021": true
5+
},
6+
"extends": [
7+
"eslint:recommended",
8+
"plugin:react/recommended",
9+
"plugin:@typescript-eslint/recommended",
10+
],
11+
"parser": "@typescript-eslint/parser",
12+
"parserOptions": {
13+
"ecmaVersion": "latest",
14+
"sourceType": "module"
15+
},
16+
"plugins": [
17+
"react",
18+
"@typescript-eslint",
19+
"prettier"
20+
],
21+
"settings": {
22+
"react": {
23+
"version": "detect"
24+
}
25+
},
26+
}

.github/workflows/main.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI
2+
on: [push]
3+
jobs:
4+
build:
5+
name: Build, lint, and test
6+
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- name: Checkout repo
11+
uses: actions/checkout@v2
12+
13+
- name: Use Node 16
14+
uses: actions/setup-node@v1
15+
with:
16+
node-version: 16
17+
18+
- name: Get yarn cache
19+
id: yarn-cache
20+
run: echo "::set-output name=dir::$(yarn cache dir)"
21+
22+
- uses: actions/cache@v2
23+
with:
24+
path: ${{ steps.yarn-cache.outputs.dir }}
25+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
26+
restore-keys: |
27+
${{ runner.os }}-yarn-
28+
29+
- name: Install dependencies
30+
run: yarn install
31+
32+
- name: Lint
33+
run: yarn lint
34+
35+
- name: Test
36+
run: yarn test --ci --coverage --maxWorkers=2
37+
38+
- name: Build
39+
run: yarn build
40+
41+
- name: Upload build artifact
42+
uses: actions/upload-artifact@v2
43+
with:
44+
name: artifact
45+
path: dist

.github/workflows/size.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: size
2+
on: [pull_request]
3+
jobs:
4+
size:
5+
runs-on: ubuntu-latest
6+
env:
7+
CI_JOB_NUMBER: 1
8+
steps:
9+
- uses: actions/checkout@v1
10+
- uses: andresz1/size-limit-action@v1
11+
with:
12+
github_token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
*.log
2+
.DS_Store
3+
node_modules
4+
.cache
5+
dist
6+
.idea
7+
.env

.husky/pre-commit

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
yarn lint
5+
yarn test
6+
yarn build

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Piotr Oleś
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.

README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<div align="center">
2+
3+
<img width="100" height="100" src="media/react-logo.svg" alt="React logo">
4+
5+
<h1>use-transition-effect</h1>
6+
<p>Run long effects without blocking the main thread</p>
7+
8+
[![npm version](https://img.shields.io/npm/v/use-transition-effect.svg)](https://www.npmjs.com/package/use-transition-effect)
9+
[![build status](https://github.com/piotr-oles/use-transition-effect/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/piotr-oles/use-transition-effect/actions?query=branch%3Amain+event%3Apush)
10+
11+
</div>
12+
13+
## Motivation
14+
15+
Let's say you want to render something complex on a canvas in a React application.
16+
Canvas API is imperative, so to interact with it, you need to use the `useEffect()` hook.
17+
Unfortunately, if rendering takes too long, you can block the main thread and make your
18+
application unresponsive (especially on low-end devices).
19+
20+
The `useTransitionEffect()` hook provides a way to split long-running effects into smaller chunks
21+
to unblock the main thread. It uses [scheduler](https://www.npmjs.com/package/scheduler) package (from React)
22+
to schedule smaller units of work and coordinate it with React rendering.
23+
24+
## Installation
25+
26+
This package requires [React 17+](https://www.npmjs.com/package/react) and [scheduler 0.20+](https://www.npmjs.com/package/scheduler)
27+
28+
```sh
29+
# with npm
30+
npm install use-transition-effect
31+
32+
# with yarn
33+
yarn add use-transition-effect
34+
```
35+
36+
## Usage
37+
38+
```typescript
39+
const [
40+
isPending,
41+
startTransitionEffect,
42+
stopTransitionEffect,
43+
] = useTransitionEffect();
44+
```
45+
46+
The API is very similar to the `useTransition` hook from React.
47+
It returns a stateful value for the pending state of the transition effect, a function to start it, and a function to stop it.
48+
49+
`startTransitionEffect` lets you schedule a long-running effect without blocking the main thread. It expects a generator
50+
function as an argument, so you can yield to unblock the main thread. The generator function receives the `shouldYield` function,
51+
which returns true if the current task takes too long:
52+
53+
```typescript
54+
startTransitionEffect(function*(shouldYield) {
55+
for (let item of items) {
56+
doSomeComplexSideEffects(item);
57+
if (shouldYield()) {
58+
yield;
59+
}
60+
}
61+
});
62+
```
63+
64+
Additionally, you can yield and return a cleanup function that will run on transition stop (including unmount):
65+
```typescript
66+
startTransitionEffect(function*(shouldYield) {
67+
const cleanup = () => cleanupSideEffects();
68+
69+
for (let item of items) {
70+
doSomeComplexSideEffects(item);
71+
if (shouldYield()) {
72+
yield cleanup;
73+
}
74+
}
75+
return cleanup;
76+
});
77+
```
78+
79+
`stopTransitionEffect` lets you stop the current long-running effect. You can use it as a `useEffect` cleanup:
80+
81+
```typescript
82+
useEffect(() => {
83+
startTransitionEffect(function*() {
84+
// effect
85+
});
86+
87+
return () => stopTransitionEffect();
88+
}, []);
89+
```
90+
91+
`isPending` indicates when a transition effect is active to show a pending state:
92+
93+
```tsx
94+
function App() {
95+
const [
96+
isPending,
97+
startTransitionEffect,
98+
stopTransitionEffect,
99+
] = useTransitionEffect();
100+
101+
function handleStartClick() {
102+
startTransitionEffect(function*() {
103+
// do stuff, for example render something on a canvas
104+
});
105+
}
106+
function handleStopClick() {
107+
stopTransitionEffect();
108+
}
109+
110+
return (
111+
<div>
112+
{isPending && <Spinner />}
113+
<button onClick={handleStartClick} disabled={isPending}>
114+
Start
115+
</button>
116+
<button onClick={handleStopClick} disabled={!isPending}>
117+
Stop
118+
</button>
119+
</div>
120+
);
121+
}
122+
```
123+
124+
## License
125+
126+
MIT

jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'jsdom',
5+
};

media/react-logo.svg

Lines changed: 9 additions & 0 deletions
Loading

package.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"name": "use-transition-effect",
3+
"version": "0.1.0",
4+
"license": "MIT",
5+
"author": "Piotr Oleś",
6+
"main": "dist/use-transition-effect.js",
7+
"module": "dist/use-transition-effect.mjs",
8+
"typings": "dist/use-transition-effect.d.ts",
9+
"repository": "https://github.com/piotr-oles/use-transition-effect.git",
10+
"files": [
11+
"dist"
12+
],
13+
"engines": {
14+
"node": ">=10"
15+
},
16+
"scripts": {
17+
"build": "rimraf dist && rollup -c rollup.config.js",
18+
"test": "jest",
19+
"lint": "eslint src test",
20+
"prepare": "husky install",
21+
"size": "size-limit"
22+
},
23+
"peerDependencies": {
24+
"react": ">=17",
25+
"scheduler": ">=0.20"
26+
},
27+
"size-limit": [
28+
{
29+
"path": "dist/use-transition-effect.cjs.production.min.js",
30+
"limit": "1 KB"
31+
},
32+
{
33+
"path": "dist/use-transition-effect.esm.js",
34+
"limit": "1 KB"
35+
}
36+
],
37+
"devDependencies": {
38+
"@rollup/plugin-typescript": "^8.3.2",
39+
"@size-limit/preset-small-lib": "^7.0.8",
40+
"@testing-library/react": "^13.3.0",
41+
"@types/jest": "^28.1.0",
42+
"@types/react": "^18.0.9",
43+
"@types/react-dom": "^18.0.5",
44+
"@types/scheduler": "^0.16.2",
45+
"@typescript-eslint/eslint-plugin": "^5.27.0",
46+
"@typescript-eslint/parser": "^5.27.0",
47+
"eslint": "^8.16.0",
48+
"eslint-plugin-prettier": "^4.0.0",
49+
"eslint-plugin-react": "^7.30.0",
50+
"husky": "^8.0.1",
51+
"jest": "^28.1.0",
52+
"jest-environment-jsdom": "^28.1.0",
53+
"prettier": "^2.6.2",
54+
"react": "^18.1.0",
55+
"react-dom": "^18.1.0",
56+
"rimraf": "^3.0.2",
57+
"rollup": "^2.75.5",
58+
"scheduler": "^0.22.0",
59+
"size-limit": "^7.0.8",
60+
"ts-jest": "^28.0.3",
61+
"tslib": "^2.4.0",
62+
"typescript": "^4.7.2"
63+
}
64+
}

0 commit comments

Comments
 (0)