├── .gitignore ├── .watchmanconfig ├── README.md ├── babel.config.js ├── license.md ├── package.json ├── tsconfig.json ├── tsconfig.production.json ├── useRefState.test.ts ├── useRefState.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamthesiz/urs/7e0e2825bcb5c5484cfc57bed58e48ca31d4ba95/.watchmanconfig -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

useRefState

2 |

🔥 React hook for maintaining correct values, in a clean way, without updates on unmounted components

3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | undefined 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

23 | 24 | Features 25 | -------- 26 | 27 | - TypeScript support 28 | - Zero dependencies 29 | - React Native support 30 | - Keep your state consistant within your callback functions 31 | 32 | 34 | 35 | Installation 36 | ------------ 37 | 38 | ```shell 39 | yarn add urs or npm i -S urs 40 | ``` 41 | 42 | Usage 43 | ----- 44 | 45 | ```jsx 46 | import useRefState from 'urs' 47 | import { useState } from 'react' 48 | 49 | const App = () => { 50 | const [loadingRef, setLoadingRef] = useRefState(false) 51 | const [loadingState, setLoadingState] = useState(false) 52 | 53 | // DO NOT destructure like this 54 | const [{ current }] = useRefState() 55 | 56 | const onClick = () => { 57 | setLoadingRef(true) 58 | console.log('loadingRef.current', loadingRef.current) // gives us `true` 59 | setLoadingState(true) 60 | console.log('loadingState', loadingState) // gives us `false` 61 | } 62 | 63 | return ( 64 | 65 | ) 66 | } 67 | ``` 68 | 69 | Options 70 | ------- 71 | 72 | The 2nd argument of `useRefState` determines if you want to be able to update state when a component 73 | is unmounted. If `true` it will block setState on unmounted components. Useful for the common error `cannot update state on unmounted component`. 74 | 75 | ```js 76 | const [state, setState] = useRefState('same as useState default state', true) 77 | ``` 78 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = { 3 | plugins: [ 4 | ['@babel/plugin-proposal-decorators', { legacy: true }], 5 | ['@babel/plugin-proposal-class-properties', { loose: true }], 6 | '@babel/plugin-syntax-dynamic-import', 7 | '@babel/plugin-transform-regenerator', 8 | [ 9 | '@babel/plugin-transform-runtime', 10 | { 11 | helpers: false, 12 | regenerator: true, 13 | }, 14 | ], 15 | ], 16 | presets: [ 17 | '@babel/preset-env', 18 | 'module:metro-react-native-babel-preset', 19 | '@babel/preset-react', 20 | '@babel/typescript', 21 | ], 22 | }; -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alex Cory 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "urs", 3 | "version": "0.0.8", 4 | "description": "🔥 React hook for maintaining correct values, in a clean way", 5 | "main": "dist/useRefState.js", 6 | "homepage": "https://github.com/ava/urs#readme", 7 | "scripts": { 8 | "dev": "rimraf dist && parcel examples/index.html --open", 9 | "build": "rimraf dist && tsc --module CommonJS", 10 | "build:production": "yarn build -p tsconfig.production.json", 11 | "build:watch": "rimraf dist && tsc -w --module CommonJS", 12 | "prepublishOnly": "yarn test && yarn build:production", 13 | "test": "tsc -p . --noEmit && tsc -p . && jest --env=jsdom useRefState.test.ts", 14 | "test:browser:watch": "tsc -p . --noEmit && tsc -p . && jest --watch --env=jsdom useRefState.test.ts" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/alex-cory/urs.git" 19 | }, 20 | "author": "Alex Cory ", 21 | "license": "MIT", 22 | "private": false, 23 | "peerDependencies": { 24 | "react": "^16.13.1 || ^17.0.0", 25 | "react-dom": "^16.13.1 || ^17.0.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/plugin-proposal-class-properties": "^7.8.3", 29 | "@babel/plugin-proposal-decorators": "^7.8.3", 30 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 31 | "@babel/plugin-transform-regenerator": "^7.8.7", 32 | "@babel/plugin-transform-runtime": "^7.9.0", 33 | "@babel/preset-react": "^7.9.4", 34 | "@babel/preset-typescript": "^7.9.0", 35 | "@testing-library/react-hooks": "^3.2.1", 36 | "@types/jest": "^25.1.5", 37 | "@types/react": "^16.9.32", 38 | "@types/react-dom": "^16.9.6", 39 | "@types/react-native": "^0.62.1", 40 | "babel-jest": "^25.2.6", 41 | "babel-preset-react-native": "^4.0.1", 42 | "jest": "^25.2.6", 43 | "metro-react-native-babel-preset": "^0.59.0", 44 | "parcel-bundler": "^1.12.4", 45 | "react": "^16.13.1", 46 | "react-native": "^0.62.0", 47 | "react-native-typescript-transformer": "^1.2.13", 48 | "react-test-renderer": "^16.13.1", 49 | "react-testing-library": "^8.0.1", 50 | "rimraf": "^3.0.2", 51 | "ts-jest": "^25.3.0", 52 | "typescript": "^3.8.3" 53 | }, 54 | "jest": { 55 | "preset": "react-native", 56 | "transform": { 57 | ".(ts|tsx)": "ts-jest" 58 | }, 59 | "testEnvironment": "node", 60 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 61 | "testPathIgnorePatterns": [ 62 | "/node_modules/", 63 | "/dist/" 64 | ], 65 | "moduleFileExtensions": [ 66 | "ts", 67 | "tsx", 68 | "js" 69 | ], 70 | "coveragePathIgnorePatterns": [ 71 | "/node_modules/", 72 | "/dist/" 73 | ], 74 | "coverageThreshold": { 75 | "global": { 76 | "branches": 90, 77 | "functions": 95, 78 | "lines": 95, 79 | "statements": 95 80 | } 81 | }, 82 | "collectCoverageFrom": [ 83 | "src/*.{js,ts}" 84 | ] 85 | }, 86 | "files": [ 87 | "dist" 88 | ], 89 | "bugs": { 90 | "url": "https://github.com/alex-cory/urs/issues" 91 | }, 92 | "keywords": [ 93 | "ssr", 94 | "nextjs", 95 | "server", 96 | "server side rendering", 97 | "server side", 98 | "client", 99 | "browser", 100 | "isomorphic", 101 | "react", 102 | "react-hook", 103 | "hooks", 104 | "react hooks" 105 | ], 106 | "dependencies": {} 107 | } 108 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "jsx": "react", 11 | "esModuleInterop": true, 12 | "types": [ 13 | "jest" 14 | ] 15 | }, 16 | "include": [ 17 | "useRefState.ts" 18 | ], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "target": "es5", 6 | "lib": ["dom", "es2015"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /useRefState.test.ts: -------------------------------------------------------------------------------- 1 | import useRState from './useRefState'; 2 | 3 | describe('useRefState', () => { 4 | it('should be defined', () => { 5 | expect(useRState).toBeDefined() 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /useRefState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef, useState, useEffect, MutableRefObject, Dispatch, SetStateAction } from 'react' 2 | 3 | /** 4 | * Determines if the given param is an object. {} 5 | * @param obj 6 | */ 7 | export const isObject = (obj: any): obj is object => Object.prototype.toString.call(obj) === '[object Object]' // eslint-disable-line 8 | 9 | const useMounted = () => { 10 | const mounted = useRef(false) 11 | useEffect(() => { 12 | mounted.current = true 13 | return () => { 14 | mounted.current = false 15 | } 16 | }, []) 17 | return mounted 18 | } 19 | 20 | export function useRefState( 21 | initialState: S | (() => S), 22 | blockIfUnmounted: boolean = true 23 | ): [MutableRefObject, Dispatch>] { 24 | const mounted = useMounted() 25 | const [reactState, setReactState] = useState(initialState) 26 | const state = useRef(reactState) 27 | const setState = useCallback(arg => { 28 | if (!mounted.current && blockIfUnmounted) return 29 | state.current = (typeof arg === 'function') ? arg(state.current) : arg 30 | setReactState(state.current) 31 | }, []) 32 | return [state, setState] 33 | } 34 | 35 | export default useRefState 36 | --------------------------------------------------------------------------------