Compare commits
3 Commits
a9f9678133
...
main
Author | SHA1 | Date | |
---|---|---|---|
467455c49a | |||
30d6d1dddd | |||
dc95275086 |
@ -1,3 +1,7 @@
|
|||||||
# demos-react-animated-list
|
# demos-react-animated-list
|
||||||
|
|
||||||
Demonstration of a solution for animating a re-ordered React list.
|
Demonstration of a solution for animating a re-ordered React list using refs, local state, and a layout effect.
|
||||||
|
|
||||||
|
The source code here complements [this blog post](https://matthewcardarelli.com/blog/dev-tricks/2024/08/07/react-animated-list.html). It is for educational purposes, and should not be incorporated into other programs without robust testing.
|
||||||
|
|
||||||
|
All files in this repository are distributed under the MIT License.
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
"name": "demo-react-animated-list",
|
"name": "demo-react-animated-list",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A demo of an implementation of an animated list in React Typescript.",
|
"description": "A demo of an implementation of an animated list in React Typescript.",
|
||||||
"main": "index.tsx",
|
"main": "src/index.tsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild index.tsx --bundle --sourcemap --outfile=./dist/demo-react-animated-list.js",
|
"build": "esbuild ./src/index.tsx --bundle --sourcemap --outfile=./dist/demo-react-animated-list.js",
|
||||||
"dev": "esbuild index.tsx --bundle --watch --serve --outdir=./public/scripts --servedir=./public",
|
"dev": "esbuild ./src/index.tsx --bundle --watch --serve --outdir=./public/scripts --servedir=./public",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -3,6 +3,16 @@ import { useAnimatedListItems } from './useAnimatedListItems';
|
|||||||
|
|
||||||
type Mutation = 'reverse' | 'randomize' | 'insert' | 'remove';
|
type Mutation = 'reverse' | 'randomize' | 'insert' | 'remove';
|
||||||
|
|
||||||
|
function getRandomChar(): string {
|
||||||
|
return String.fromCharCode(98 + Math.floor(Math.random() * 25));
|
||||||
|
}
|
||||||
|
function getRandomItemName(): string {
|
||||||
|
return `Serial Number - ${new Array(16)
|
||||||
|
.fill('')
|
||||||
|
.map(() => getRandomChar())
|
||||||
|
.join('')}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Randomizer function shamelessly copied unmodified from https://stackoverflow.com/a/12646864
|
* Randomizer function shamelessly copied unmodified from https://stackoverflow.com/a/12646864
|
||||||
*
|
*
|
||||||
@ -21,7 +31,7 @@ function shuffleArray(array) {
|
|||||||
|
|
||||||
function insertRandomlyIntoArray(array: string[]): string[] {
|
function insertRandomlyIntoArray(array: string[]): string[] {
|
||||||
const insertBefore = Math.floor(Math.random() * (array.length + 1));
|
const insertBefore = Math.floor(Math.random() * (array.length + 1));
|
||||||
const newItem = `Serial Number ${crypto.randomUUID()}`;
|
const newItem = getRandomItemName();
|
||||||
if (insertBefore === 0) {
|
if (insertBefore === 0) {
|
||||||
return [newItem, ...array];
|
return [newItem, ...array];
|
||||||
}
|
}
|
||||||
@ -65,19 +75,15 @@ export const AnimatedListDemo = () => {
|
|||||||
}
|
}
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
[
|
new Array(4).fill('').map(() => getRandomItemName()),
|
||||||
`Serial Number ${crypto.randomUUID()}`,
|
|
||||||
`Serial Number ${crypto.randomUUID()}`,
|
|
||||||
`Serial Number ${crypto.randomUUID()}`,
|
|
||||||
`Serial Number ${crypto.randomUUID()}`,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { updateItemRef, updateItemPositions, itemStyles } =
|
const { updateItemRef, updateItemPositions, itemStyles } =
|
||||||
useAnimatedListItems({ keys: items });
|
useAnimatedListItems({ keys: items });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<>
|
||||||
|
<div className="buttons">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -114,7 +120,7 @@ export const AnimatedListDemo = () => {
|
|||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<ol>
|
<ol>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
return (
|
return (
|
||||||
@ -128,6 +134,6 @@ export const AnimatedListDemo = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ol>
|
</ol>
|
||||||
</main>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -10,7 +10,7 @@ class AnimatedListComponent extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
const rootNode = document.createElement('main');
|
const rootNode = document.createElement('section');
|
||||||
this.appendChild(rootNode);
|
this.appendChild(rootNode);
|
||||||
this.root = createRoot(rootNode);
|
this.root = createRoot(rootNode);
|
||||||
this.root.render(<AnimatedListDemo />);
|
this.root.render(<AnimatedListDemo />);
|
@ -8,5 +8,11 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"alwaysStrict": true
|
"alwaysStrict": true
|
||||||
},
|
},
|
||||||
"include": ["*.ts", "*.tsx"]
|
"include": [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"src/index.tsx",
|
||||||
|
"src/AnimatedListDemo.tsx",
|
||||||
|
"src/useAnimatedListItems.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user