├── .buckconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── demo
└── useStateWithLayoutAnimation.gif
├── metro.config.js
├── package.json
├── src
├── __tests__
│ └── spring.test.ts
├── index.ts
├── types.ts
└── useStateWithLayoutAnimation.ts
├── tsconfig.json
└── yarn.lock
/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native-community',
4 | parser: '@typescript-eslint/parser',
5 | plugins: ['@typescript-eslint'],
6 | env: {'jest/globals': true},
7 | };
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | npm-publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repo
12 | uses: actions/checkout@master
13 | - name: Set up NodeJS
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: '12'
17 | - name: Build package
18 | run: |
19 | npm i -g yarn
20 | yarn install
21 | yarn build
22 | - name: Publish
23 | uses: JS-DevTools/npm-publish@v1
24 | with:
25 | token: ${{ secrets.NPM_AUTH_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 |
24 | # Android/IntelliJ
25 | #
26 | build/
27 | .idea
28 | .gradle
29 | local.properties
30 | *.iml
31 |
32 | # Visual Studio Code
33 | #
34 | .vscode/
35 |
36 | # node.js
37 | #
38 | node_modules/
39 | npm-debug.log
40 | yarn-error.log
41 |
42 | # BUCK
43 | buck-out/
44 | \.buckd/
45 | *.keystore
46 | !debug.keystore
47 |
48 | # fastlane
49 | #
50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
51 | # screenshots whenever they are needed.
52 | # For more information about the recommended setup visit:
53 | # https://docs.fastlane.tools/best-practices/source-control/
54 |
55 | */fastlane/report.xml
56 | */fastlane/Preview.html
57 | */fastlane/screenshots
58 |
59 | # Bundle artifact
60 | *.jsbundle
61 |
62 | # CocoaPods
63 | /ios/Pods/
64 | dist/
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
7 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Karl Marx Lopez
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # useStateWithLayoutAnimation
4 | Abstraction for `React Native`'s `LayoutAnimation` and `useState`
5 |
6 | ## Install
7 | `yarn add use-state-with-layout-animation`
8 |
9 | Or
10 |
11 | `npm install --save use-state-with-layout-animation`
12 |
13 | ## Example
14 |
15 | 
16 |
17 | [Download expo client and scan the QR code to run the snack on your `iOS` or `Android` device](https://snack.expo.io/@iamkarlmarx/usestatewithlayoutanimation). (It does not work on web)
18 |
19 | ## API
20 |
21 | ### `useStateWithLayoutAnimation`
22 | By default, `UIManager.setLayoutAnimationEnabledExperimental` is invoked, you can pass `false` as the second parameter if you want to call it on your own.
23 | ```
24 | const [state, setState] = useStateWithLayoutAnimation(123, false);
25 | ```
26 | ### `setState.spring`
27 | ### `setState.linear`
28 | ### `setState.easeInEaseOut`
29 | You can use this the same as `useState` setter, accepts values or optional callback function but accepts a second parameter for the animation finish callback.
30 | ```ts
31 | const [state, setState] = useStateWithLayoutAnimation(1);
32 |
33 | const animationDidFinish = () => console.log('Animation finished');
34 |
35 | setState.spring(2, animationDidFinish);
36 | setState.linear(prev => prev + 10, animationDidFinish);
37 | setState.easeInEaseOut(4, animationDidFinish);
38 | ```
39 |
40 | ### `setState.noAnimation`
41 | You can use this the same as `useState` setter, accepts values or optional callback function.
42 | ```ts
43 | setState.noAnimation(4);
44 | setState.noAnimation(prev => prev + 1);
45 | ```
46 |
47 |
48 | ## License
49 | [MIT](https://github.com/karlmarxlopez/useStateWithLayoutAnimation/blob/master/LICENSE)
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ['@commitlint/config-conventional']}
2 |
--------------------------------------------------------------------------------
/demo/useStateWithLayoutAnimation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaghettiC0des/useStateWithLayoutAnimation/97b4eab27c7b82995e1a1801bf0a64f92ed68a7e/demo/useStateWithLayoutAnimation.gif
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: false,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-state-with-layout-animation",
3 | "version": "1.0.8",
4 | "private": false,
5 | "license": "MIT",
6 | "author": {
7 | "name": "Karl Marx Lopez",
8 | "email": "karlmarx.webdev@gmail.com"
9 | },
10 | "repository": {
11 | "url": "https://github.com/karlmarxlopez/useStateWithLayoutAnimation"
12 | },
13 | "keywords": [
14 | "react",
15 | "react hooks",
16 | "animation",
17 | "react-native",
18 | "hooks"
19 | ],
20 | "main": "dist/index.js",
21 | "module": "dist/index.js",
22 | "types": "dist/index.d.ts",
23 | "files": [
24 | "dist"
25 | ],
26 | "scripts": {
27 | "build": "tsc",
28 | "test": "jest",
29 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
30 | },
31 | "peerDependencies": {
32 | "react": "16.13.1",
33 | "react-native": "0.63.2"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.8.4",
37 | "@babel/runtime": "^7.8.4",
38 | "@commitlint/cli": "^9.1.2",
39 | "@commitlint/config-conventional": "^9.1.2",
40 | "@react-native-community/eslint-config": "^1.1.0",
41 | "@testing-library/react-hooks": "^3.4.2",
42 | "@testing-library/react-native": "^7.0.2",
43 | "@types/jest": "^26.0.14",
44 | "@types/jest-in-case": "^1.0.2",
45 | "@types/react-native": "^0.63.2",
46 | "@types/react-test-renderer": "^16.9.2",
47 | "@typescript-eslint/eslint-plugin": "^2.27.0",
48 | "@typescript-eslint/parser": "^2.27.0",
49 | "babel-jest": "^26.3.0",
50 | "eslint": "^6.5.1",
51 | "husky": ">=4",
52 | "jest": "^26.4.2",
53 | "jest-in-case": "^1.0.2",
54 | "lint-staged": ">=10",
55 | "metro-react-native-babel-preset": "^0.59.0",
56 | "prettier": "^2.0.4",
57 | "react": "16.13.1",
58 | "react-native": "0.63.2",
59 | "react-test-renderer": "16.13.1",
60 | "typescript": "^3.8.3"
61 | },
62 | "jest": {
63 | "preset": "react-native",
64 | "moduleFileExtensions": [
65 | "ts",
66 | "tsx",
67 | "js",
68 | "jsx",
69 | "json",
70 | "node"
71 | ]
72 | },
73 | "husky": {
74 | "hooks": {
75 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
76 | "pre-commit": "lint-staged"
77 | }
78 | },
79 | "lint-staged": {
80 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix",
81 | "*.{js,css,md}": "prettier --write"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/__tests__/spring.test.ts:
--------------------------------------------------------------------------------
1 | import {act, renderHook} from '@testing-library/react-hooks';
2 | import {LayoutAnimation} from 'react-native';
3 | import cases from 'jest-in-case';
4 | import useStateWithLayoutAnimation from '../useStateWithLayoutAnimation';
5 |
6 | jest.mock(
7 | '../../node_modules/react-native/Libraries/LayoutAnimation/LayoutAnimation.js',
8 | );
9 |
10 | const mock = jest.fn();
11 |
12 | beforeEach(() => jest.clearAllMocks());
13 |
14 | cases(
15 | 'should methods change state and call callback',
16 | ({name, callback}) => {
17 | const {result} = renderHook(() => useStateWithLayoutAnimation(1));
18 | const setState = result.current[1];
19 |
20 | act(() => {
21 | setState[name](2, callback);
22 | });
23 |
24 | const animationMock = LayoutAnimation[name];
25 | const state = result.current[0];
26 |
27 | expect(state).toBe(2);
28 | expect(animationMock).toBeCalledTimes(1);
29 | expect(animationMock.mock.calls[0][0]).toBe(callback);
30 | },
31 | [
32 | {name: 'spring', callback: mock},
33 | {name: 'spring', callback: undefined},
34 | {name: 'linear', callback: mock},
35 | {name: 'linear', callback: undefined},
36 | {name: 'easeInEaseOut', callback: mock},
37 | {name: 'easeInEaseOut', callback: undefined},
38 | ],
39 | );
40 |
41 | test('should noAnimation method change only state', () => {
42 | const {result} = renderHook(() => useStateWithLayoutAnimation(1));
43 | const setState = result.current[1];
44 |
45 | act(() => {
46 | setState.noAnimation(2);
47 | });
48 |
49 | const state = result.current[0];
50 |
51 | expect(state).toBe(2);
52 | expect(LayoutAnimation.spring).toBeCalledTimes(0);
53 | expect(LayoutAnimation.linear).toBeCalledTimes(0);
54 | expect(LayoutAnimation.easeInEaseOut).toBeCalledTimes(0);
55 | });
56 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {default} from './useStateWithLayoutAnimation';
2 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import {SetStateAction} from 'react';
2 |
3 | export type OnAnimationDidEndFn = () => void;
4 |
5 | export type LayoutAnimationFn = (
6 | onAnimationDidEnd?: OnAnimationDidEndFn,
7 | ) => void;
8 |
9 | export type SetStateWithLayoutAnimation = (
10 | nextState: SetStateAction,
11 | onAnimationDidEnd?: OnAnimationDidEndFn,
12 | ) => void;
13 |
14 | export type StateSetter = {
15 | spring: SetStateWithLayoutAnimation;
16 | linear: SetStateWithLayoutAnimation;
17 | easeInEaseOut: SetStateWithLayoutAnimation;
18 | noAnimation: (nextState: SetStateAction) => void;
19 | };
20 |
--------------------------------------------------------------------------------
/src/useStateWithLayoutAnimation.ts:
--------------------------------------------------------------------------------
1 | import {useState, useRef, useEffect, SetStateAction, useCallback} from 'react';
2 | import {LayoutAnimation, NativeModules} from 'react-native';
3 | import {StateSetter, LayoutAnimationFn, OnAnimationDidEndFn} from './types';
4 |
5 | const {UIManager} = NativeModules;
6 |
7 | const useStateWithLayoutAnimation = (
8 | initialValue: S,
9 | setLayoutAnimationEnabledExperimental = true,
10 | ): [S, StateSetter] => {
11 | useEffect(() => {
12 | if (setLayoutAnimationEnabledExperimental) {
13 | UIManager.setLayoutAnimationEnabledExperimental &&
14 | UIManager.setLayoutAnimationEnabledExperimental(true);
15 | }
16 | }, [setLayoutAnimationEnabledExperimental]);
17 |
18 | const [state, setState] = useState(initialValue);
19 |
20 | const partialSetState = useCallback(
21 | (animationFn: LayoutAnimationFn) => (
22 | nextState: SetStateAction,
23 | onAnimationDidEnd?: OnAnimationDidEndFn,
24 | ) => {
25 | animationFn(onAnimationDidEnd);
26 | setState(nextState);
27 | },
28 | [],
29 | );
30 |
31 | const stateSetters = useRef({
32 | spring: partialSetState(LayoutAnimation.spring),
33 | linear: partialSetState(LayoutAnimation.linear),
34 | easeInEaseOut: partialSetState(LayoutAnimation.easeInEaseOut),
35 | noAnimation: setState,
36 | }).current;
37 |
38 | return [state, stateSetters];
39 | };
40 |
41 | export default useStateWithLayoutAnimation;
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | "lib": ["es6"], /* Specify library files to be included in the compilation. */
7 | "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | "jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | // "sourceMap": true, /* Generates corresponding '.map' file. */
12 | // "outFile": "./", /* Concatenate and emit output to single file. */
13 | "outDir": "./dist", /* Redirect output structure to the directory. */
14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
15 | // "removeComments": true, /* Do not emit comments to output. */
16 | "noEmit": false, /* Do not emit outputs. */
17 | // "incremental": true, /* Enable incremental compilation */
18 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
19 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
20 | "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
21 |
22 | /* Strict Type-Checking Options */
23 | "strict": true, /* Enable all strict type-checking options. */
24 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
25 | // "strictNullChecks": true, /* Enable strict null checks. */
26 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
27 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
28 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
29 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
30 |
31 | /* Additional Checks */
32 | // "noUnusedLocals": true, /* Report errors on unused locals. */
33 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
34 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
35 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
36 |
37 | /* Module Resolution Options */
38 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
39 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
40 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
41 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
42 | // "typeRoots": [], /* List of folders to include type definitions from. */
43 | // "types": [], /* Type declaration files to be included in compilation. */
44 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
45 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
46 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
47 |
48 | /* Source Map Options */
49 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
50 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
51 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
52 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
53 |
54 | /* Experimental Options */
55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
57 | },
58 | "include": ["src/*.ts"],
59 | "exclude": [
60 | "node_modules", "babel.config.js", "metro.config.js", "jest.config.js"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------