├── .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 |
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 |
--------------------------------------------------------------------------------