├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── example ├── compare-context │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── components │ │ ├── Row.js │ │ ├── RowWithImprovedContext.jsx │ │ └── RowWithRegularContext.jsx │ │ ├── context.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ ├── setupTests.js │ │ └── store.js └── counter │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── report.20200419.154956.68014.001.json │ └── src │ ├── App.js │ └── index.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── .eslintrc ├── createContext.test.ts ├── createContext.ts ├── createContextConsumer.test.tsx ├── createContextConsumer.ts ├── declarations.d.ts ├── globals.ts ├── index.ts ├── isEqualShallow.test.ts ├── isEqualShallow.ts ├── react-app-env.d.ts ├── useContextSelection.test.tsx └── useContextSelection.ts ├── tsconfig.json └── tsconfig.test.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "ecmaFeatures": { 17 | "legacyDecorators": true, 18 | "jsx": true 19 | } 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "16" 24 | } 25 | }, 26 | "rules": { 27 | "space-before-function-paren": 0, 28 | "react/prop-types": 0, 29 | "react/jsx-handler-names": 0, 30 | "react/jsx-fragments": 0, 31 | "react/no-unused-prop-types": 0, 32 | "import/export": 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "printWidth": 120 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # use-context-selection 2 | 3 | ## IMPORTANT NOTE! 4 | 5 | > This library won't work as expected for `React >= 18`, as the `unstable_changedBits` property from `createContext` is no longer supported. 6 | > 7 | > The recommendation is to migrate to [use-context-selector](https://www.npmjs.com/package/use-context-selector) library instead, which uses a different approach for scheduling updates when a part of your whole state gets updated. 8 | 9 | 10 | 11 | - [use-context-selection](#use-context-selection) 12 | - [Install](#install) 13 | - [What?](#what) 14 | - [Why?](#why) 15 | - [How?](#how) 16 | - [Usage](#usage) 17 | - [Demo App](#demo-app) 18 | - [API](#api) 19 | - [createContext](#createcontext) 20 | - [useContextSelection](#usecontextselection) 21 | - [isEqualShallow](#isequalshallow) 22 | - [Related projects](#related-projects) 23 | - [Disclaimer](#disclaimer) 24 | - [License](#license) 25 | 26 | 27 | 28 | ## Install 29 | 30 | ```bash 31 | npm install --save use-context-selection 32 | ``` 33 | 34 | ## What? 35 | 36 | `use-context-selection` allows your components to select partial data from Context and receive updates only when that part of your Context value changes. 37 | 38 | ## Why? 39 | 40 | By using `useContext` hook your components subscribes to the entire Context value and will receive updates anytime one of the values in the store changes. 41 | 42 | As an example, let's suppose you have data `a`, `b` and `c` stored in your context, and 3 components `A`, `B` and `C` which respectively access that data. Then, suppose that `a` value in the Context is modified; this will trigger a re-render on the tree components (`A`, `B` and `C`). 43 | 44 | This might not be an issue if your application only connects to the Context from a small number of components, but things can get slowish for other applications which connect a lot of components with the store. 45 | 46 | To overcome this issue, `useContextSelection` let you subscribe to any piece of data on your Context and dispatch updates to your components only when it's related data changes! This means, in the example above, changes on `a` will result in ONLY component `A` to be re-rendered. 47 | 48 | ## How? 49 | 50 | This library makes use of a non-documented feature available on `React.createContext` API which allows us to disable dispatching updates to every component accessing a Context value. Then, thanks to hooks, we can dispatch updates specifically to the components listening for some changes. 51 | 52 | When `useContextSelection` is used on a component it will register this component as a listener of the Context; then, when a Context value is updated it will detect the changed data and dispatch an update to the components listening for the specific piece of data. 53 | 54 | `useContextSelection` receives a getter function as argument to get whatever you want from the Context; e.g.: 55 | 56 | ```javascript 57 | // Let's suppose this is the current state of your Context data 58 | const state = { 59 | a: 'A value', 60 | b: 'B value', 61 | c: 'B value', 62 | } 63 | 64 | // Then, in component `A` you can select (and listen) only `a` value 65 | const a = useContextSelection(state => state.a); 66 | ``` 67 | 68 | And that's it! Now, every time `state.a` changes then component `A` will get re-rendered. 69 | 70 | NOTE: you can return anything on your getter function, so for example this is also valid: 71 | ```javascript 72 | const { a, b } = useContextSelection(state => ({ a: state.a, b: state.b }); 73 | ``` 74 | 75 | or even this: 76 | ```javascript 77 | const [ a, b ] = useContextSelection(state => [state.a, state.b]); 78 | ``` 79 | 80 | ## Usage 81 | 82 | First, you need to create a new `Context` using the `createContext` function included in this library (using `React.createContext` will throw an error). 83 | 84 | ```javascript 85 | import { createContext, useContextSelection } from 'use-context-selection'; 86 | 87 | const AuthContext = createContext({}); 88 | ``` 89 | 90 | Then, you can use `Context` as you would normally do on your application; here is just an example but you can implement your provider as you want (e.g. using `React.useReducer`): 91 | 92 | ```javascript 93 | const AuthProvider = ({ children }) => { 94 | const [user, setUser] = React.useState(null); 95 | const [isLoading, setIsLoading] = React.useState(false); 96 | 97 | function loginFn(username) { 98 | setIsLoading(true); 99 | 100 | setTimeout(() => { 101 | setUser({ username }); 102 | setIsLoading(false); 103 | }, 1000); 104 | } 105 | 106 | const contextValue = { 107 | user, 108 | loginFn, 109 | isLoading, 110 | isAuthenticated: Boolean(user), 111 | }; 112 | 113 | return ( 114 | {children} 115 | ); 116 | } 117 | ``` 118 | 119 | Now, from you component you can listen for specific parts of the state: 120 | 121 | ```javascript 122 | const AppContent = () => { 123 | const { isLoading, loginFn } = useContextSelection(AuthContext, state => ({ 124 | isLoading: state.isLoading, 125 | loginFn: state.loginFn, 126 | })); 127 | 128 | if (isLoading) { 129 | return 'Loading...'; 130 | } 131 | return ; 132 | } 133 | ``` 134 | 135 | Finally, remember to wrap your application with the Provider. 136 | 137 | ```javascript 138 | export default App = () => { 139 | 140 | 141 | 142 | } 143 | ``` 144 | 145 | 146 | Or you can also use a selection function using `Context.Consumer` component in this way: 147 | 148 | ```javascript 149 | const App = () => ( 150 | 151 | ({ isLoading: state.isLoading, loginFn: state.loginFn })}> 152 | {({ isLoading, loginFn }) => { 153 | if (isLoading) { 154 | return 'Loading...'; 155 | } 156 | return ; 157 | }} 158 | 159 | 160 | ); 161 | ``` 162 | 163 | ## Demo App 164 | 165 | Check performance comparison against `useContext` hook on the following app-example: 166 | [https://edriang.github.io/use-context-selection/](https://edriang.github.io/use-context-selection/) 167 | 168 | 169 | ## API 170 | 171 | ### createContext 172 | 173 | Creates a smart `Context` object which compares changes on your Context state and dispatches changes to subscribers. 174 | 175 | | Param | Type | Description | Optional / Required | 176 | |-------|-------------|-------------|---------------| 177 | | initValue | any | Initial value for the Context | Required | 178 | | equalityFn | Function | Function used to compare old vs new state; by default it performs shallow equality check | Optional | 179 | 180 | - **Return Value**: Context 181 | 182 | ### useContextSelection 183 | 184 | Hook to access your Context state; receives a `Context` object created with `createContext` function and a `selection` function. 185 | 186 | This Hook will trigger a re-render on your component every-time these conditions are met: 187 | - The state on your `Context` changes 188 | - The `selection` function returns a different value since the last time it rendered 189 | 190 | | Param | Type | Description | Optional / Required | 191 | |-------|-------------|-------------|---------------| 192 | | Context | Context | Context created with `createContext` function from this library | Required | 193 | | selection | Function | Use this selection function to retrieve data from your Context; receives the current state / value and should return whatever your component needs | Required | 194 | 195 | - **Return Value**: any; whatever you are returning on `selection` function. 196 | 197 | ### isEqualShallow 198 | 199 | This is the default comparator function used internally if `equalityFn` param is not provided to `createContext`. 200 | 201 | This function is exported as part of the library in case you need it as foundations for your own equality check function. 202 | 203 | You need to remember two things about this default equality function: 204 | - As the name already implies, it performs a **shallow** equality check for performance reassons; 205 | - It will ignore comparing `functions`; this comes handy as you'd probably include in your store functions to mutate the current state; this way there is no need to memoize the functions (e.g. using `React.useCallback`). 206 | 207 | | Param | Type | Description | Optional / Required | 208 | |-------|-------------|-------------|---------------| 209 | | newState | any | New state to compare with | Required | 210 | | oldState | any | Old state to compare with | Required | 211 | 212 | - **Return Value**: boolean; whether both states are considered the same or not. 213 | 214 | ## Related projects 215 | 216 | This library is used internally by [react-connect-context-hooks](https://www.npmjs.com/package/react-connect-context-hooks), a library for easily managing application-state. 217 | 218 | ## Disclaimer 219 | 220 | This library was inspired by [use-context-selector](https://www.npmjs.com/package/use-context-selector), but here are some key differences: 221 | - `use-context-selection` allows you to specify a custom `equality-check` function. 222 | - Internally, `use-context-selection` manages different set of listeners per Context, while `use-context-selector` stores all listeners (even for different Contexts) within a single shared Set. 223 | - `use-context-selection` allows you to use an enhanced version of `Context.Consumer` component, which supports `selection` functions like `useContextSelection` hook. 224 | - `use-context-selector` [executes different logic](https://github.com/dai-shi/use-context-selector/blob/3f1df8f818176db835dc894fff191e9a4d2f8a68/src/index.js#L9) when running in `production` mode than while in `development`. In production mode it'll trigger updates on listener components during the `render` phase; this behavior is something React would complain about with some scary warning messages on the console if it runs on `development` mode. 225 | 226 | That being said, based on some internal testing, both libraries are seamlessly performant. 227 | 228 | ## License 229 | 230 | MIT © [edriang](https://github.com/edriang) 231 | -------------------------------------------------------------------------------- /example/compare-context/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/compare-context/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /example/compare-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ignore-compare-context", 3 | "version": "0.1.2", 4 | "private": true, 5 | "homepage": "https://edriang.github.io/use-context-selection", 6 | "dependencies": { 7 | "moment": "^2.24.0", 8 | "react": "file:../../node_modules/react", 9 | "react-dom": "file:../../node_modules/react-dom", 10 | "react-scripts": "file:../../node_modules/react-scripts", 11 | "use-context-selection": "file:../.." 12 | }, 13 | "scripts": { 14 | "start": "node ../../node_modules/react-scripts/bin/react-scripts.js start", 15 | "build": "node ../../node_modules/react-scripts/bin/react-scripts.js build", 16 | "test": "node ../../node_modules/react-scripts/bin/react-scripts.js test", 17 | "eject": "node ../../node_modules/react-scripts/bin/react-scripts.js eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "acorn": "^7.1.1", 36 | "minimist": "^1.2.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/compare-context/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edriang/use-context-selection/be2138586a20493c5f1b322a8863cc4cdd5a977e/example/compare-context/public/favicon.ico -------------------------------------------------------------------------------- /example/compare-context/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /example/compare-context/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edriang/use-context-selection/be2138586a20493c5f1b322a8863cc4cdd5a977e/example/compare-context/public/logo192.png -------------------------------------------------------------------------------- /example/compare-context/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edriang/use-context-selection/be2138586a20493c5f1b322a8863cc4cdd5a977e/example/compare-context/public/logo512.png -------------------------------------------------------------------------------- /example/compare-context/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example/compare-context/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/compare-context/src/App.css: -------------------------------------------------------------------------------- 1 | .container-xl { 2 | max-width: 1400px; 3 | } 4 | 5 | .buttons-disabled .btn { 6 | opacity: 0.5; 7 | pointer-events: none; 8 | } 9 | 10 | @media (max-width: 1200px) { 11 | body { 12 | font-size: 14px; 13 | } 14 | } 15 | 16 | .App { 17 | text-align: center; 18 | padding: 50px; 19 | } 20 | 21 | .App-logo { 22 | height: 40vmin; 23 | pointer-events: none; 24 | } 25 | 26 | @media (prefers-reduced-motion: no-preference) { 27 | .App-logo { 28 | animation: App-logo-spin infinite 20s linear; 29 | } 30 | } 31 | 32 | .App-header { 33 | background-color: #282c34; 34 | min-height: 100vh; 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | justify-content: center; 39 | font-size: calc(10px + 2vmin); 40 | color: white; 41 | } 42 | 43 | .App-link { 44 | color: #61dafb; 45 | } 46 | 47 | .input-number { 48 | display: block; 49 | max-width: 400px; 50 | margin: 10px auto 25px; 51 | } 52 | 53 | @keyframes App-logo-spin { 54 | from { 55 | transform: rotate(0deg); 56 | } 57 | to { 58 | transform: rotate(360deg); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/compare-context/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './App.css'; 4 | 5 | import { RegularProvider, ImprovedProvider } from './context'; 6 | 7 | import RowWithRegularContext from './components/RowWithRegularContext'; 8 | import RowWithImprovedContext from './components/RowWithImprovedContext'; 9 | 10 | const NUM_ROWS = 300; 11 | 12 | function simulateClicks(tableClassName, setButtonsDisabled) { 13 | const buttons = document.querySelectorAll(`.table.${tableClassName} .btn`); 14 | const startTime = Date.now(); 15 | 16 | setButtonsDisabled(true); 17 | 18 | clickNextButton(buttons, 0, startTime, () => setButtonsDisabled(false)); 19 | } 20 | 21 | function clickNextButton(buttons, index, startTime, onFinish) { 22 | if (index === buttons.length) { 23 | onFinish(); 24 | alert(`Render finished: ${Date.now() - startTime}ms`) 25 | return; 26 | } 27 | buttons[index].click(); 28 | 29 | window.requestAnimationFrame(() => { 30 | clickNextButton(buttons, index + 1, startTime, onFinish); 31 | }); 32 | } 33 | 34 | function App() { 35 | const [numOfRows, setNumOfRows] = React.useState(NUM_ROWS); 36 | const [buttonsDisabled, setButtonsDisabled] = React.useState(false); 37 | 38 | return ( 39 |
40 |
41 |

Performance comparison

42 |

Enter the number of rows you want to render

43 | 44 |
45 | setNumOfRows(parseInt(e.target.value || 0))} /> 46 |
47 | 48 |

On the left side, each value is read directly from Context. On the right side, values are read from Context using use-context-selection.

49 |

For each row we render we are displaying the rendered time and a the last time that cell was updated.

50 |

When you click a button its updated-time value is updated and a re-render is triggered. Note that for regular Context that means re-rendering all the rows even though its cell-value didn't change.

51 |

With use-context-selection you can retrieve chunks from your Context and your components will update only when that value is updated, which improves the performance significantly.

52 |
53 |

54 | The "Simulate Click All" button will "click", one by one, on every row-button. 55 |
56 | After clicking on every button an alert message will be displayed showing the amount of time it took to render every row. 57 |
58 | To experience a real difference in performance enter more than 150 rows; on the left example the time complexity increases exponentially, while on the right example it increases linearly. 59 |

60 |
61 |
62 |
63 |

64 | Regular Context 65 | 66 |

67 | 68 | 69 | 70 | {(new Array(numOfRows).fill(0).map((_, index) => ( 71 | 72 | )))} 73 | 74 |
75 |
76 |
77 |
78 |

79 | useContextSelection 80 | 81 |

82 | 83 | {/* ({ length: state.rows.length })}> 84 | {({ length }) => String(length)} 85 | */} 86 | 87 | 88 | {(new Array(numOfRows).fill(0).map((_, index) => ( 89 | 90 | )))} 91 | 92 |
93 |
94 |
95 |
96 |
97 | ); 98 | } 99 | 100 | export default App; 101 | -------------------------------------------------------------------------------- /example/compare-context/src/components/Row.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | 4 | const Component = ({ value, setValue, name }) => { 5 | const updateValue = () => { 6 | setValue(Date.now()); 7 | } 8 | 9 | return ( 10 | 11 | 12 | {name} 13 | 14 | 15 | Rendered: 16 |

{moment().format('hh:mm:ss.SSS')}

17 | 18 | 19 | Updated: 20 |

{moment(value).format('hh:mm:ss.SSS')}

21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export default Component; 31 | -------------------------------------------------------------------------------- /example/compare-context/src/components/RowWithImprovedContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Row from './Row'; 4 | import { useImprovedContext } from '../context'; 5 | 6 | const RowWithImprovedContext = React.memo(({ name, index }) => { 7 | const { rowValue, setRowValue } = useImprovedContext(store => { 8 | // console.log('useImprovedContext', { store }); 9 | return { 10 | rowValue: store.rows[index], 11 | setRowValue: store.setRowValue, 12 | }; 13 | }); 14 | const setValue = React.useCallback((value) => setRowValue(index, value), [index]); 15 | 16 | return 17 | }); 18 | 19 | export default RowWithImprovedContext; 20 | -------------------------------------------------------------------------------- /example/compare-context/src/components/RowWithRegularContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Row from './Row'; 4 | import { useRegularContext } from '../context'; 5 | 6 | const RowWithRegularContext = React.memo(({ name, index }) => { 7 | const { rows, setRowValue } = useRegularContext(); 8 | 9 | const setValue = React.useCallback((value) => setRowValue(index, value), [index]); 10 | 11 | return 12 | }); 13 | 14 | export default RowWithRegularContext; 15 | -------------------------------------------------------------------------------- /example/compare-context/src/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createContext, useContextSelection } from 'use-context-selection'; 3 | 4 | function createContextProvider(Context) { 5 | return ({ children, numOfRows}) => { 6 | 7 | const [state, dispatch] = React.useReducer((state, { type, payload = {} }) => { 8 | switch (type) { 9 | case 'INCREMENT': 10 | const rows = state.rows.slice(0); 11 | 12 | rows[payload.index] = payload.value; 13 | 14 | return { ...state, rows }; 15 | 16 | case 'SET_ROW_NUMBER': 17 | return { 18 | ...state, 19 | rows: new Array(payload.numOfRows).fill(0).map(() => Date.now()), 20 | }; 21 | default: 22 | return state; 23 | } 24 | }, { rows: [] }) 25 | 26 | const setRowValue = (index, value) => { 27 | dispatch({ 28 | type: 'INCREMENT', 29 | payload: { index, value }, 30 | }) 31 | }; 32 | 33 | React.useEffect(() => { 34 | dispatch({ type: 'SET_ROW_NUMBER', payload: { numOfRows }}) 35 | }, [numOfRows]); 36 | 37 | const value = { 38 | rows: state.rows, 39 | setRowValue, 40 | }; 41 | 42 | return {children} 43 | }; 44 | } 45 | 46 | const RegularContext = React.createContext({}); 47 | const RegularProvider = createContextProvider(RegularContext); 48 | const useRegularContext = () => React.useContext(RegularContext); 49 | 50 | 51 | const ImprovedContext = createContext({}); 52 | const ImprovedProvider = createContextProvider(ImprovedContext); 53 | const useImprovedContext = (getterFn) => useContextSelection(ImprovedContext, getterFn); 54 | 55 | export { 56 | RegularProvider, 57 | useRegularContext, 58 | 59 | ImprovedContext, 60 | ImprovedProvider, 61 | useImprovedContext, 62 | }; 63 | -------------------------------------------------------------------------------- /example/compare-context/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/compare-context/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /example/compare-context/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/compare-context/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /example/compare-context/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /example/compare-context/src/store.js: -------------------------------------------------------------------------------- 1 | import createContextProvider, { connectContextFactory } from 'react-connect-context-hooks'; 2 | 3 | import { rows } from './constants'; 4 | 5 | const initialState = { 6 | rows, 7 | }; 8 | 9 | const actions = { 10 | setRowValue: (dispatch, getState) => (index, value) => { 11 | const { rows } = getState(); 12 | 13 | rows[index] = value; 14 | 15 | dispatch({ rows: rows.slice(0) }); 16 | }, 17 | }; 18 | 19 | const [StoreProvider, Context] = createContextProvider(initialState, actions); 20 | const withStore = connectContextFactory(Context); 21 | 22 | export default StoreProvider; 23 | export { 24 | withStore, 25 | }; 26 | -------------------------------------------------------------------------------- /example/counter/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the use-context-selection package in the parent directory for development purposes. 4 | 5 | You can run `npm install` and then `npm start` to test your package. 6 | -------------------------------------------------------------------------------- /example/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-context-selection-example", 3 | "homepage": "https://edriang.github.io/use-context-selection", 4 | "version": "0.0.0", 5 | "private": true, 6 | "dependencies": { 7 | "react": "file:../../node_modules/react", 8 | "react-dom": "file:../../node_modules/react-dom", 9 | "react-scripts": "file:../../node_modules/react-scripts", 10 | "use-context-selection": "file:../.." 11 | }, 12 | "scripts": { 13 | "start": "node ../../node_modules/react-scripts/bin/react-scripts.js start", 14 | "build": "node ../../node_modules/react-scripts/bin/react-scripts.js build", 15 | "test": "node ../../node_modules/react-scripts/bin/react-scripts.js test", 16 | "eject": "node ../../node_modules/react-scripts/bin/react-scripts.js eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": [ 22 | ">0.2%", 23 | "not dead", 24 | "not ie <= 11", 25 | "not op_mini all" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /example/counter/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edriang/use-context-selection/be2138586a20493c5f1b322a8863cc4cdd5a977e/example/counter/public/favicon.ico -------------------------------------------------------------------------------- /example/counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | use-context-selection 28 | 29 | 30 | 31 | 34 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/counter/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "use-context-selection", 3 | "name": "use-context-selection", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/counter/report.20200419.154956.68014.001.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "header": { 4 | "event": "Allocation failed - JavaScript heap out of memory", 5 | "trigger": "FatalError", 6 | "filename": "report.20200419.154956.68014.001.json", 7 | "dumpEventTime": "2020-04-19T15:49:56Z", 8 | "dumpEventTimeStamp": "1587329396416", 9 | "processId": 68014, 10 | "commandLine": [ 11 | "node", 12 | "/Users/adriangallardo/Projects/_own/use-context-selection/node_modules/react-scripts/scripts/start.js" 13 | ], 14 | "nodejsVersion": "v11.12.0", 15 | "wordSize": 64, 16 | "arch": "x64", 17 | "platform": "darwin", 18 | "componentVersions": { 19 | "node": "11.12.0", 20 | "v8": "7.0.276.38-node.18", 21 | "uv": "1.26.0", 22 | "zlib": "1.2.11", 23 | "brotli": "1.0.7", 24 | "ares": "1.15.0", 25 | "modules": "67", 26 | "nghttp2": "1.34.0", 27 | "napi": "4", 28 | "llhttp": "1.1.1", 29 | "http_parser": "2.8.0", 30 | "openssl": "1.1.1b", 31 | "cldr": "34.0", 32 | "icu": "63.1", 33 | "tz": "2018e", 34 | "unicode": "11.0" 35 | }, 36 | "release": { 37 | "name": "node", 38 | "headersUrl": "https://nodejs.org/download/release/v11.12.0/node-v11.12.0-headers.tar.gz", 39 | "sourceUrl": "https://nodejs.org/download/release/v11.12.0/node-v11.12.0.tar.gz" 40 | }, 41 | "osName": "Darwin", 42 | "osRelease": "18.6.0", 43 | "osVersion": "Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64", 44 | "osMachine": "x86_64", 45 | "host": "Adrians-MacBook-Pro-3.local" 46 | }, 47 | "javascriptStack": { 48 | "message": "No stack.", 49 | "stack": [ 50 | "Unavailable." 51 | ] 52 | }, 53 | "nativeStack": [ 54 | { 55 | "pc": "0x00000001001382e2", 56 | "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string, std::__1::allocator > const&, v8::Local) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 57 | }, 58 | { 59 | "pc": "0x0000000100066f24", 60 | "symbol": "node::OnFatalError(char const*, char const*) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 61 | }, 62 | { 63 | "pc": "0x00000001001b50d7", 64 | "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 65 | }, 66 | { 67 | "pc": "0x00000001001b5074", 68 | "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 69 | }, 70 | { 71 | "pc": "0x00000001005bbfb2", 72 | "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 73 | }, 74 | { 75 | "pc": "0x00000001005be4e3", 76 | "symbol": "v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 77 | }, 78 | { 79 | "pc": "0x00000001005baa18", 80 | "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 81 | }, 82 | { 83 | "pc": "0x00000001005b8bd5", 84 | "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 85 | }, 86 | { 87 | "pc": "0x00000001005c547c", 88 | "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 89 | }, 90 | { 91 | "pc": "0x00000001005c54ff", 92 | "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 93 | }, 94 | { 95 | "pc": "0x00000001005946f4", 96 | "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 97 | }, 98 | { 99 | "pc": "0x0000000100846f94", 100 | "symbol": "v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node]" 101 | }, 102 | { 103 | "pc": "0x000030e9f054fc7d", 104 | "symbol": "" 105 | }, 106 | { 107 | "pc": "0x000030e9f055f90b", 108 | "symbol": "" 109 | }, 110 | { 111 | "pc": "0x000030e9f0c44a76", 112 | "symbol": "" 113 | } 114 | ], 115 | "javascriptHeap": { 116 | "totalMemory": 1504935936, 117 | "totalCommittedMemory": 1499993208, 118 | "usedMemory": 1237902016, 119 | "availableMemory": 41504216, 120 | "memoryLimit": 1526909922, 121 | "heapSpaces": { 122 | "read_only_space": { 123 | "memorySize": 524288, 124 | "committedMemory": 42224, 125 | "capacity": 515584, 126 | "used": 33520, 127 | "available": 482064 128 | }, 129 | "new_space": { 130 | "memorySize": 33554432, 131 | "committedMemory": 32522760, 132 | "capacity": 16498688, 133 | "used": 1090376, 134 | "available": 15408312 135 | }, 136 | "old_space": { 137 | "memorySize": 1415254016, 138 | "committedMemory": 1412378128, 139 | "capacity": 1188497184, 140 | "used": 1184665360, 141 | "available": 3831824 142 | }, 143 | "code_space": { 144 | "memorySize": 4718592, 145 | "committedMemory": 4559104, 146 | "capacity": 4088416, 147 | "used": 4088416, 148 | "available": 0 149 | }, 150 | "map_space": { 151 | "memorySize": 4206592, 152 | "committedMemory": 3812976, 153 | "capacity": 2813520, 154 | "used": 2813520, 155 | "available": 0 156 | }, 157 | "large_object_space": { 158 | "memorySize": 46678016, 159 | "committedMemory": 46678016, 160 | "capacity": 66992840, 161 | "used": 45210824, 162 | "available": 21782016 163 | }, 164 | "new_large_object_space": { 165 | "memorySize": 0, 166 | "committedMemory": 0, 167 | "capacity": 0, 168 | "used": 0, 169 | "available": 0 170 | } 171 | } 172 | }, 173 | "resourceUsage": { 174 | "userCpuSeconds": 104.188, 175 | "kernelCpuSeconds": 57.3819, 176 | "cpuConsumptionPercent": 237.603, 177 | "maxRss": 1734504087552, 178 | "pageFaults": { 179 | "IORequired": 15, 180 | "IONotRequired": 604588 181 | }, 182 | "fsActivity": { 183 | "reads": 0, 184 | "writes": 0 185 | } 186 | }, 187 | "libuv": [ 188 | ], 189 | "environmentVariables": { 190 | "npm_config_save_dev": "", 191 | "npm_config_legacy_bundling": "", 192 | "npm_config_dry_run": "", 193 | "npm_config_viewer": "man", 194 | "npm_config_only": "", 195 | "npm_config_commit_hooks": "true", 196 | "npm_config_browser": "", 197 | "npm_config_also": "", 198 | "npm_config_sign_git_commit": "", 199 | "npm_config_rollback": "true", 200 | "TERM_PROGRAM": "vscode", 201 | "NODE": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node", 202 | "npm_config_usage": "", 203 | "npm_config_audit": "true", 204 | "INIT_CWD": "/Users/adriangallardo/Projects/_own/use-context-selection/example/counter", 205 | "npm_package_homepage": "https://edriang.github.io/use-context-selection", 206 | "NVM_CD_FLAGS": "-q", 207 | "PYENV_ROOT": "/Users/adriangallardo/.pyenv", 208 | "npm_config_globalignorefile": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/etc/npmignore", 209 | "TERM": "xterm-256color", 210 | "SHELL": "/bin/zsh", 211 | "npm_config_shell": "/bin/zsh", 212 | "npm_config_maxsockets": "50", 213 | "npm_config_init_author_url": "", 214 | "npm_config_shrinkwrap": "true", 215 | "npm_config_parseable": "", 216 | "npm_config_metrics_registry": "https://registry.npmjs.org/", 217 | "TMPDIR": "/var/folders/9y/nnz6w85s7q1dlx8g15lg_g6c0000gn/T/", 218 | "npm_config_timing": "", 219 | "npm_config_init_license": "ISC", 220 | "Apple_PubSub_Socket_Render": "/private/tmp/com.apple.launchd.D9EHkyBTbg/Render", 221 | "npm_config_if_present": "", 222 | "TERM_PROGRAM_VERSION": "1.44.1", 223 | "npm_config_sign_git_tag": "", 224 | "npm_config_init_author_email": "", 225 | "npm_config_cache_max": "Infinity", 226 | "npm_config_preid": "", 227 | "npm_config_long": "", 228 | "npm_config_local_address": "", 229 | "npm_config_git_tag_version": "true", 230 | "npm_config_cert": "", 231 | "TERM_SESSION_ID": "6035200E-04B5-4CE3-995C-E44C650CE62C", 232 | "npm_config_registry": "https://registry.npmjs.org/", 233 | "npm_config_noproxy": "", 234 | "npm_config_fetch_retries": "2", 235 | "npm_package_private": "true", 236 | "npm_package_dependencies_react_dom": "file:../../node_modules/react-dom", 237 | "ZSH": "/Users/adriangallardo/.oh-my-zsh", 238 | "npm_config_versions": "", 239 | "npm_config_message": "%s", 240 | "npm_config_key": "", 241 | "npm_package_readmeFilename": "README.md", 242 | "npm_package_description": "This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).", 243 | "NVM_DIR": "/Users/adriangallardo/.nvm", 244 | "USER": "adriangallardo", 245 | "COMMAND_MODE": "unix2003", 246 | "npm_config_globalconfig": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/etc/npmrc", 247 | "npm_config_prefer_online": "", 248 | "npm_config_logs_max": "10", 249 | "npm_config_always_auth": "", 250 | "npm_package_eslintConfig_extends": "react-app", 251 | "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.CSLzGXqpdI/Listeners", 252 | "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0", 253 | "npm_execpath": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/lib/node_modules/npm/bin/npm-cli.js", 254 | "npm_config_global_style": "", 255 | "npm_config_cache_lock_retries": "10", 256 | "npm_config_update_notifier": "true", 257 | "npm_config_cafile": "", 258 | "PAGER": "less", 259 | "npm_config_heading": "npm", 260 | "npm_config_audit_level": "low", 261 | "LSCOLORS": "Gxfxcxdxbxegedabagacad", 262 | "npm_config_searchlimit": "20", 263 | "npm_config_read_only": "", 264 | "npm_config_offline": "", 265 | "npm_config_fetch_retry_mintimeout": "10000", 266 | "npm_config_json": "", 267 | "npm_config_access": "", 268 | "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}", 269 | "PATH": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/adriangallardo/Projects/_own/use-context-selection/example/counter/node_modules/.bin:/Users/adriangallardo/.pyenv/shims:/Users/adriangallardo/.pyenv/bin:/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/adriangallardo/.pyenv/shims:/Users/adriangallardo/.pyenv/bin:/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin", 270 | "npm_config_allow_same_version": "", 271 | "npm_config_https_proxy": "", 272 | "npm_config_engine_strict": "", 273 | "npm_config_description": "true", 274 | "_": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node", 275 | "npm_config_userconfig": "/Users/adriangallardo/.npmrc", 276 | "npm_config_init_module": "/Users/adriangallardo/.npm-init.js", 277 | "npm_config_cidr": "", 278 | "PWD": "/Users/adriangallardo/Projects/_own/use-context-selection/example/counter", 279 | "npm_config_user": "501", 280 | "npm_config_node_version": "11.12.0", 281 | "npm_lifecycle_event": "start", 282 | "npm_config_save": "true", 283 | "npm_config_ignore_prepublish": "", 284 | "npm_config_editor": "vi", 285 | "npm_config_auth_type": "legacy", 286 | "npm_package_name": "use-context-selection-example", 287 | "LANG": "en_US.UTF-8", 288 | "npm_config_tag": "latest", 289 | "npm_config_script_shell": "", 290 | "npm_config_progress": "true", 291 | "npm_config_global": "", 292 | "npm_package_scripts_build": "node ../../node_modules/react-scripts/bin/react-scripts.js build", 293 | "npm_package_scripts_start": "node ../../node_modules/react-scripts/bin/react-scripts.js start", 294 | "npm_config_searchstaleness": "900", 295 | "npm_config_optional": "true", 296 | "npm_config_ham_it_up": "", 297 | "XPC_FLAGS": "0x0", 298 | "npm_config_save_prod": "", 299 | "npm_config_force": "", 300 | "npm_config_bin_links": "true", 301 | "npm_config_searchopts": "", 302 | "npm_config_node_gyp": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js", 303 | "npm_config_depth": "Infinity", 304 | "npm_config_sso_poll_frequency": "500", 305 | "npm_config_rebuild_bundle": "true", 306 | "npm_package_version": "0.0.0", 307 | "XPC_SERVICE_NAME": "0", 308 | "npm_config_unicode": "true", 309 | "SHLVL": "2", 310 | "PYENV_SHELL": "zsh", 311 | "HOME": "/Users/adriangallardo", 312 | "npm_config_fetch_retry_maxtimeout": "60000", 313 | "npm_package_scripts_test": "node ../../node_modules/react-scripts/bin/react-scripts.js test", 314 | "APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL": "true", 315 | "npm_config_tag_version_prefix": "v", 316 | "npm_config_strict_ssl": "true", 317 | "npm_config_sso_type": "oauth", 318 | "npm_config_scripts_prepend_node_path": "warn-only", 319 | "npm_config_save_prefix": "^", 320 | "npm_config_loglevel": "notice", 321 | "npm_config_ca": "", 322 | "npm_config_save_exact": "", 323 | "npm_config_group": "20", 324 | "npm_config_fetch_retry_factor": "10", 325 | "npm_config_dev": "", 326 | "npm_package_browserslist_3": "not op_mini all", 327 | "npm_config_version": "", 328 | "npm_config_prefer_offline": "", 329 | "npm_config_cache_lock_stale": "60000", 330 | "npm_package_browserslist_2": "not ie <= 11", 331 | "npm_config_otp": "", 332 | "npm_config_cache_min": "10", 333 | "npm_package_browserslist_1": "not dead", 334 | "npm_config_searchexclude": "", 335 | "npm_config_cache": "/Users/adriangallardo/.npm", 336 | "npm_package_browserslist_0": ">0.2%", 337 | "npm_package_dependencies_react_scripts": "file:../../node_modules/react-scripts", 338 | "LESS": "-R", 339 | "LOGNAME": "adriangallardo", 340 | "npm_lifecycle_script": "node ../../node_modules/react-scripts/bin/react-scripts.js start", 341 | "npm_config_color": "true", 342 | "npm_config_proxy": "", 343 | "npm_config_package_lock": "true", 344 | "LC_CTYPE": "UTF-8", 345 | "npm_config_package_lock_only": "", 346 | "npm_package_dependencies_react": "file:../../node_modules/react", 347 | "npm_config_save_optional": "", 348 | "npm_package_dependencies_use_context_with_selector": "file:../..", 349 | "NVM_BIN": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin", 350 | "npm_config_ignore_scripts": "", 351 | "npm_config_user_agent": "npm/6.7.0 node/v11.12.0 darwin x64", 352 | "npm_config_cache_lock_wait": "10000", 353 | "npm_config_production": "", 354 | "npm_config_send_metrics": "", 355 | "npm_config_save_bundle": "", 356 | "npm_config_umask": "0022", 357 | "npm_config_node_options": "", 358 | "npm_config_init_version": "1.0.0", 359 | "npm_config_init_author_name": "", 360 | "npm_config_git": "git", 361 | "npm_config_scope": "", 362 | "npm_package_scripts_eject": "node ../../node_modules/react-scripts/bin/react-scripts.js eject", 363 | "SECURITYSESSIONID": "186a7", 364 | "npm_config_unsafe_perm": "true", 365 | "npm_config_tmp": "/var/folders/9y/nnz6w85s7q1dlx8g15lg_g6c0000gn/T", 366 | "npm_config_onload_script": "", 367 | "npm_node_execpath": "/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node", 368 | "npm_config_prefix": "/Users/adriangallardo/.nvm/versions/node/v11.12.0", 369 | "npm_config_link": "", 370 | "COLORTERM": "truecolor", 371 | "BABEL_ENV": "development", 372 | "NODE_ENV": "development", 373 | "NODE_PATH": "", 374 | "WEBPACK_DEV_SERVER": "true" 375 | }, 376 | "userLimits": { 377 | "core_file_size_blocks": { 378 | "soft": 0, 379 | "hard": "unlimited" 380 | }, 381 | "data_seg_size_kbytes": { 382 | "soft": "unlimited", 383 | "hard": "unlimited" 384 | }, 385 | "file_size_blocks": { 386 | "soft": "unlimited", 387 | "hard": "unlimited" 388 | }, 389 | "max_locked_memory_bytes": { 390 | "soft": "unlimited", 391 | "hard": "unlimited" 392 | }, 393 | "max_memory_size_kbytes": { 394 | "soft": "unlimited", 395 | "hard": "unlimited" 396 | }, 397 | "open_files": { 398 | "soft": 24576, 399 | "hard": "unlimited" 400 | }, 401 | "stack_size_bytes": { 402 | "soft": 8388608, 403 | "hard": 67104768 404 | }, 405 | "cpu_time_seconds": { 406 | "soft": "unlimited", 407 | "hard": "unlimited" 408 | }, 409 | "max_user_processes": { 410 | "soft": 1418, 411 | "hard": 2128 412 | }, 413 | "virtual_memory_kbytes": { 414 | "soft": "unlimited", 415 | "hard": "unlimited" 416 | } 417 | }, 418 | "sharedObjects": [ 419 | "/Users/adriangallardo/.nvm/versions/node/v11.12.0/bin/node", 420 | "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", 421 | "/usr/lib/libSystem.B.dylib", 422 | "/usr/lib/libc++.1.dylib", 423 | "/usr/lib/libobjc.A.dylib", 424 | "/usr/lib/libDiagnosticMessagesClient.dylib", 425 | "/usr/lib/libicucore.A.dylib", 426 | "/usr/lib/libz.1.dylib", 427 | "/usr/lib/libc++abi.dylib", 428 | "/usr/lib/system/libcache.dylib", 429 | "/usr/lib/system/libcommonCrypto.dylib", 430 | "/usr/lib/system/libcompiler_rt.dylib", 431 | "/usr/lib/system/libcopyfile.dylib", 432 | "/usr/lib/system/libcorecrypto.dylib", 433 | "/usr/lib/system/libdispatch.dylib", 434 | "/usr/lib/system/libdyld.dylib", 435 | "/usr/lib/system/libkeymgr.dylib", 436 | "/usr/lib/system/liblaunch.dylib", 437 | "/usr/lib/system/libmacho.dylib", 438 | "/usr/lib/system/libquarantine.dylib", 439 | "/usr/lib/system/libremovefile.dylib", 440 | "/usr/lib/system/libsystem_asl.dylib", 441 | "/usr/lib/system/libsystem_blocks.dylib", 442 | "/usr/lib/system/libsystem_c.dylib", 443 | "/usr/lib/system/libsystem_configuration.dylib", 444 | "/usr/lib/system/libsystem_coreservices.dylib", 445 | "/usr/lib/system/libsystem_darwin.dylib", 446 | "/usr/lib/system/libsystem_dnssd.dylib", 447 | "/usr/lib/system/libsystem_info.dylib", 448 | "/usr/lib/system/libsystem_m.dylib", 449 | "/usr/lib/system/libsystem_malloc.dylib", 450 | "/usr/lib/system/libsystem_networkextension.dylib", 451 | "/usr/lib/system/libsystem_notify.dylib", 452 | "/usr/lib/system/libsystem_sandbox.dylib", 453 | "/usr/lib/system/libsystem_secinit.dylib", 454 | "/usr/lib/system/libsystem_kernel.dylib", 455 | "/usr/lib/system/libsystem_platform.dylib", 456 | "/usr/lib/system/libsystem_pthread.dylib", 457 | "/usr/lib/system/libsystem_symptoms.dylib", 458 | "/usr/lib/system/libsystem_trace.dylib", 459 | "/usr/lib/system/libunwind.dylib", 460 | "/usr/lib/system/libxpc.dylib", 461 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices", 462 | "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics", 463 | "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText", 464 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO", 465 | "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync", 466 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS", 467 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy", 468 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices", 469 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices", 470 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis", 471 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore", 472 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD", 473 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis", 474 | "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight", 475 | "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface", 476 | "/usr/lib/libxml2.2.dylib", 477 | "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork", 478 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate", 479 | "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation", 480 | "/usr/lib/libcompression.dylib", 481 | "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration", 482 | "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay", 483 | "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit", 484 | "/System/Library/Frameworks/Metal.framework/Versions/A/Metal", 485 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders", 486 | "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport", 487 | "/System/Library/Frameworks/Security.framework/Versions/A/Security", 488 | "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore", 489 | "/usr/lib/libbsm.0.dylib", 490 | "/usr/lib/liblzma.5.dylib", 491 | "/usr/lib/libauto.dylib", 492 | "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration", 493 | "/usr/lib/libarchive.2.dylib", 494 | "/usr/lib/liblangid.dylib", 495 | "/usr/lib/libCRFSuite.dylib", 496 | "/usr/lib/libenergytrace.dylib", 497 | "/usr/lib/system/libkxld.dylib", 498 | "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression", 499 | "/usr/lib/libOpenScriptingUtil.dylib", 500 | "/usr/lib/libcoretls.dylib", 501 | "/usr/lib/libcoretls_cfhelpers.dylib", 502 | "/usr/lib/libpam.2.dylib", 503 | "/usr/lib/libsqlite3.dylib", 504 | "/usr/lib/libxar.1.dylib", 505 | "/usr/lib/libbz2.1.0.dylib", 506 | "/usr/lib/libnetwork.dylib", 507 | "/usr/lib/libapple_nghttp2.dylib", 508 | "/usr/lib/libpcap.A.dylib", 509 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents", 510 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore", 511 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata", 512 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices", 513 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit", 514 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE", 515 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices", 516 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices", 517 | "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList", 518 | "/System/Library/Frameworks/NetFS.framework/Versions/A/NetFS", 519 | "/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth", 520 | "/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport", 521 | "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC", 522 | "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP", 523 | "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities", 524 | "/usr/lib/libmecabra.dylib", 525 | "/usr/lib/libmecab.1.0.0.dylib", 526 | "/usr/lib/libgermantok.dylib", 527 | "/usr/lib/libThaiTokenizer.dylib", 528 | "/usr/lib/libChineseTokenizer.dylib", 529 | "/usr/lib/libiconv.2.dylib", 530 | "/usr/lib/libcharset.1.dylib", 531 | "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling", 532 | "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji", 533 | "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon", 534 | "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData", 535 | "/usr/lib/libcmph.dylib", 536 | "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData", 537 | "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory", 538 | "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS", 539 | "/usr/lib/libutil.dylib", 540 | "/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement", 541 | "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement", 542 | "/usr/lib/libxslt.1.dylib", 543 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage", 544 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib", 545 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib", 546 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib", 547 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib", 548 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib", 549 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib", 550 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib", 551 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib", 552 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib", 553 | "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib", 554 | "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler", 555 | "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator", 556 | "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment", 557 | "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay", 558 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib", 559 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSCore.framework/Versions/A/MPSCore", 560 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSImage.framework/Versions/A/MPSImage", 561 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork", 562 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix", 563 | "/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector", 564 | "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools", 565 | "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary", 566 | "/usr/lib/libMobileGestalt.dylib", 567 | "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage", 568 | "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo", 569 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL", 570 | "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer", 571 | "/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore", 572 | "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL", 573 | "/usr/lib/libFosl_dynamic.dylib", 574 | "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG", 575 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontParser.dylib", 576 | "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib", 577 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib", 578 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib", 579 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib", 580 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib", 581 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib", 582 | "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib", 583 | "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG", 584 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib", 585 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib", 586 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", 587 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib", 588 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib", 589 | "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib", 590 | "/usr/lib/libcups.2.dylib", 591 | "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos", 592 | "/System/Library/Frameworks/GSS.framework/Versions/A/GSS", 593 | "/usr/lib/libresolv.9.dylib", 594 | "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal", 595 | "/usr/lib/libheimdal-asn1.dylib", 596 | "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory", 597 | "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth", 598 | "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation", 599 | "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio", 600 | "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox", 601 | "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce", 602 | "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices", 603 | "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard", 604 | "/Users/adriangallardo/Projects/_own/use-context-selection/node_modules/react-scripts/node_modules/webpack-dev-server/node_modules/fsevents/build/Release/fse.node", 605 | "/Users/adriangallardo/Projects/_own/use-context-selection/node_modules/react-scripts/node_modules/watchpack/node_modules/fsevents/build/Release/fse.node" 606 | ] 607 | } -------------------------------------------------------------------------------- /example/counter/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { createContext, useContextSelection } from 'use-context-selection' 4 | 5 | const Context = createContext({}); 6 | 7 | const CounterProvider = ({ children }) => { 8 | 9 | const [state, dispatch] = React.useReducer((state, action) => { 10 | switch (action.type) { 11 | case 'INCREMENT': 12 | return { ...state, count: state.count + 1 }; 13 | case 'INCREMENT2': 14 | return { ...state, count2: state.count2 + 1 }; 15 | default: 16 | return state; 17 | } 18 | }, { 19 | count: 0, 20 | count2: 999, 21 | }); 22 | 23 | const increment = () => dispatch({ type: 'INCREMENT' }); 24 | const increment2 = () => dispatch({ type: 'INCREMENT2' }); 25 | 26 | const value = { ...state, increment, increment2 }; 27 | 28 | return ( 29 | 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | const Counter1 = () => { 36 | console.log('Counter 1'); 37 | 38 | const { count, increment } = useContextSelection(Context, function getter1(state) { 39 | return { 40 | count: state.count, 41 | increment: state.increment, 42 | } 43 | }); 44 | 45 | return ( 46 |
47 | Count 1: {count} 48 | 49 |
50 | ) 51 | } 52 | 53 | const Counter2 = () => { 54 | console.log('Counter 2'); 55 | 56 | const increment2 = useContextSelection(Context, function getter2_1(state) { 57 | return state.increment2; 58 | }); 59 | 60 | const count2 = useContextSelection(Context, function getter2_2(state) { 61 | return state.count2; 62 | }); 63 | 64 | return ( 65 |
66 | Count 2: {count2} 67 | 68 |
69 | ) 70 | } 71 | 72 | 73 | const App = () => { 74 | return ( 75 | 76 | 77 | 78 | 79 | ) 80 | } 81 | 82 | export default App 83 | -------------------------------------------------------------------------------- /example/counter/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | ReactDOM.render(, document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-context-selection", 3 | "version": "1.5.3", 4 | "description": "Improved hook to select partial data from your Context and get updates on your components only when that specific piece of data changes.", 5 | "keywords": [ 6 | "react", 7 | "context", 8 | "hooks", 9 | "performance", 10 | "selection", 11 | "selector", 12 | "state management" 13 | ], 14 | "author": "Adrián Gallardo", 15 | "license": "MIT", 16 | "repository": "edriang/use-context-selection", 17 | "homepage": "https://edriang.github.io/use-context-selection", 18 | "main": "dist/index.js", 19 | "module": "dist/index.es.js", 20 | "jsnext:main": "dist/index.es.js", 21 | "source": "src/index.tsx", 22 | "engines": { 23 | "node": ">=10" 24 | }, 25 | "scripts": { 26 | "build": "rollup -c", 27 | "start": "rollup -c -w", 28 | "prepublish": "run-s build", 29 | "test": "run-s test:unit test:lint test:build", 30 | "test:build": "run-s build", 31 | "test:lint": "eslint .", 32 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 33 | "test:watch": "react-scripts test --env=jsdom", 34 | "predeploy": "cd example/compare-context && npm install && npm run build", 35 | "deploy": "gh-pages -d example/compare-context/build" 36 | }, 37 | "peerDependencies": { 38 | "react": "^16.8.0||^17.0.0" 39 | }, 40 | "devDependencies": { 41 | "@testing-library/react": "12.1.4", 42 | "@testing-library/react-hooks": "5.1.3", 43 | "@types/jest": "^25.1.4", 44 | "@types/react": "17.0.44", 45 | "@typescript-eslint/eslint-plugin": "^2.26.0", 46 | "@typescript-eslint/parser": "^2.26.0", 47 | "babel-eslint": "^10.0.3", 48 | "cross-env": "^7.0.2", 49 | "eslint": "^6.8.0", 50 | "eslint-config-prettier": "^6.7.0", 51 | "eslint-config-standard": "^14.1.0", 52 | "eslint-config-standard-react": "^9.2.0", 53 | "eslint-plugin-import": "^2.18.2", 54 | "eslint-plugin-node": "^11.0.0", 55 | "eslint-plugin-prettier": "^3.1.1", 56 | "eslint-plugin-promise": "^4.2.1", 57 | "eslint-plugin-react": "^7.17.0", 58 | "eslint-plugin-standard": "^4.0.1", 59 | "gh-pages": "^2.2.0", 60 | "npm-run-all": "^4.1.5", 61 | "prettier": "^2.0.4", 62 | "react": "17.0.2", 63 | "react-dom": "17.0.2", 64 | "react-scripts": "^3.4.1", 65 | "react-test-renderer": "^17.0.2", 66 | "rollup": "^2.7.1", 67 | "rollup-plugin-babel": "^4.4.0", 68 | "rollup-plugin-commonjs": "^10.1.0", 69 | "rollup-plugin-node-resolve": "^5.2.0", 70 | "rollup-plugin-peer-deps-external": "^2.2.2", 71 | "rollup-plugin-typescript2": "^0.27.0", 72 | "typescript": "^3.8.3" 73 | }, 74 | "files": [ 75 | "dist" 76 | ], 77 | "types": "./dist/index.d.ts" 78 | } 79 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import external from 'rollup-plugin-peer-deps-external'; 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | 6 | import pkg from './package.json'; 7 | 8 | export default { 9 | input: 'src/index.ts', 10 | output: [ 11 | { 12 | file: pkg.main, 13 | format: 'cjs', 14 | exports: 'named', 15 | sourcemap: true, 16 | }, 17 | { 18 | file: pkg.module, 19 | format: 'es', 20 | exports: 'named', 21 | sourcemap: true, 22 | }, 23 | ], 24 | plugins: [ 25 | external(), 26 | resolve(), 27 | typescript({ 28 | rollupCommonJSResolveHack: true, 29 | clean: true, 30 | }), 31 | commonjs(), 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/createContext.test.ts: -------------------------------------------------------------------------------- 1 | import createContext, { createContextDispatcher } from './createContext'; 2 | import { contextListeners, contextConsumerTypeSymbol } from './globals'; 3 | 4 | jest.mock('./globals', () => ({ 5 | contextConsumerTypeSymbol: Symbol('CustomConsumer'), 6 | contextListeners: new Map(), 7 | })); 8 | 9 | describe('createContext', () => { 10 | beforeEach(() => { 11 | jest.clearAllMocks(); 12 | contextListeners.clear(); 13 | }); 14 | 15 | it('creates new context and returns custom Context Object', () => { 16 | const Context = createContext({}); 17 | const typeProp = '$$type'; 18 | 19 | expect(Context.Consumer[typeProp]).toBe(contextConsumerTypeSymbol); 20 | }); 21 | 22 | it('creates new empty set for listeners associated with current Context', () => { 23 | const Context = createContext({}); 24 | 25 | expect(contextListeners.size).toBe(1); 26 | expect(contextListeners.has(Context)).toBeTruthy(); 27 | expect(contextListeners.get(Context)!.size).toBe(0); 28 | }); 29 | }); 30 | 31 | describe('createContextDispatcher', () => { 32 | it('creates comparator function', () => { 33 | const comparator = createContextDispatcher(new Set()); 34 | 35 | expect(typeof comparator).toBe('function'); 36 | }); 37 | 38 | it('comparator function returns always 0', () => { 39 | const comparator = createContextDispatcher(new Set()); 40 | 41 | expect(comparator({}, {})).toBe(0); 42 | }); 43 | 44 | it('dispatch changes to every listeners if new state is different', () => { 45 | const forceUpdate = jest.fn(); 46 | const state = { a: 'a' }; 47 | const newState = { a: 'A' }; 48 | const listener1: ContextListener = { 49 | selection: (state: any) => state, 50 | forceUpdate, 51 | }; 52 | const listener2: ContextListener = { 53 | selection: (state: any) => state, 54 | forceUpdate, 55 | }; 56 | const comparator = createContextDispatcher(new Set([listener1, listener2])); 57 | 58 | comparator(state, newState); 59 | 60 | expect(forceUpdate).toHaveBeenCalledTimes(2); 61 | expect(forceUpdate).toHaveBeenNthCalledWith(1, newState); 62 | expect(forceUpdate).toHaveBeenNthCalledWith(2, newState); 63 | }); 64 | 65 | it('dispatches to listeners only what getter returned', () => { 66 | const forceUpdate = jest.fn(); 67 | const state = { a: 'a', b: 'b' }; 68 | const newState = { a: 'A', b: 'B' }; 69 | const listener1: ContextListener = { 70 | selection: (state: any) => state.a, 71 | forceUpdate, 72 | }; 73 | const comparator = createContextDispatcher(new Set([listener1])); 74 | 75 | comparator(state, newState); 76 | 77 | expect(forceUpdate).toHaveBeenNthCalledWith(1, newState.a); 78 | }); 79 | 80 | it('does not dispatch if state changed but we are not interested on that part', () => { 81 | const forceUpdate = jest.fn(); 82 | const state = { a: 'a', b: 'b' }; 83 | const newState = { a: 'a', b: 'B' }; 84 | const listener1: ContextListener = { 85 | selection: (state: any) => state.a, 86 | forceUpdate, 87 | }; 88 | const comparator = createContextDispatcher(new Set([listener1])); 89 | 90 | comparator(state, newState); 91 | 92 | expect(forceUpdate).toHaveBeenCalledTimes(0); 93 | }); 94 | 95 | it('does not dispatch if nothing changed', () => { 96 | const forceUpdate = jest.fn(); 97 | const state = { a: 'a', b: 'b' }; 98 | const newState = { a: 'a', b: 'b' }; 99 | const listener1: ContextListener = { 100 | selection: (state: any) => state, 101 | forceUpdate, 102 | }; 103 | const comparator = createContextDispatcher(new Set([listener1])); 104 | 105 | comparator(state, newState); 106 | 107 | expect(forceUpdate).toHaveBeenCalledTimes(0); 108 | }); 109 | 110 | it('accepts custom comparator function', () => { 111 | const forceUpdate = jest.fn(); 112 | const equalityFn = () => false; 113 | const state = { a: 'a', b: 'b' }; 114 | const newState = { a: 'a', b: 'b' }; 115 | const listener1: ContextListener = { 116 | selection: (state: any) => state, 117 | forceUpdate, 118 | }; 119 | const comparator = createContextDispatcher(new Set([listener1]), equalityFn); 120 | 121 | comparator(state, newState); 122 | 123 | expect(forceUpdate).toHaveBeenCalledWith(newState); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/createContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import isEqualShallow from './isEqualShallow'; 4 | import createContextConsumer from './createContextConsumer'; 5 | import { contextListeners } from './globals'; 6 | 7 | function createContextDispatcher( 8 | listeners: Set>, 9 | equalityFn: EqualityFn = isEqualShallow 10 | ): ContextComparator { 11 | return (oldValue: T, newValue: T): 0 => { 12 | for (const listener of listeners) { 13 | const newResult = listener.selection(newValue); 14 | 15 | if (!equalityFn(newResult, listener.selection(oldValue))) { 16 | listener.forceUpdate(newResult); 17 | } 18 | } 19 | 20 | return 0; 21 | }; 22 | } 23 | 24 | function createContext(initValue: T, equalityFn?: EqualityFn): CustomContext { 25 | const listeners = new Set>(); 26 | 27 | // @ts-ignore 28 | const Context = React.createContext(initValue, createContextDispatcher(listeners, equalityFn)); 29 | 30 | contextListeners.set(Context, listeners); 31 | 32 | // @ts-ignore 33 | Context.Consumer = createContextConsumer(Context); 34 | 35 | return Context as CustomContext; 36 | } 37 | 38 | export default createContext; 39 | export { createContextDispatcher }; 40 | -------------------------------------------------------------------------------- /src/createContextConsumer.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { contextConsumerTypeSymbol } from './globals'; 5 | import createContextConsumer from './createContextConsumer'; 6 | import createContext from './createContext'; 7 | 8 | describe('createContextConsumer', () => { 9 | it('creates custom Context.Consumer', () => { 10 | const typeProp = '$$type'; 11 | const Context = createContext({}); 12 | const Consumer = createContextConsumer(Context); 13 | 14 | expect(Consumer[typeProp]).toBe(contextConsumerTypeSymbol); 15 | }); 16 | 17 | it('access to selected values from Consumer', () => { 18 | const state = { 19 | a: 'A', 20 | b: 'B', 21 | }; 22 | const Context = createContext(state); 23 | const Component = () => ( 24 | 25 | state.a}>{(a) => a} 26 | 27 | ); 28 | const { getByText } = render(); 29 | 30 | expect(getByText(state.a)).toBeTruthy(); 31 | }); 32 | }); 33 | 34 | export default createContextConsumer; 35 | -------------------------------------------------------------------------------- /src/createContextConsumer.ts: -------------------------------------------------------------------------------- 1 | import { contextConsumerTypeSymbol } from './globals'; 2 | import useContextSelection from './useContextSelection'; 3 | 4 | function createContextConsumer(Context: React.Context): ContextConsumer { 5 | const ContextSelectionConsumer: ContextConsumer = ({ children, selection = (store: any) => store }: any) => { 6 | const contextValue = useContextSelection(Context, selection); 7 | 8 | return children(contextValue); 9 | }; 10 | 11 | ContextSelectionConsumer.$$type = contextConsumerTypeSymbol; 12 | 13 | return ContextSelectionConsumer; 14 | } 15 | 16 | export default createContextConsumer; 17 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | type Selector = (state: T) => any; 2 | type ContextListener = { 3 | selection: Selector; 4 | forceUpdate: React.Dispatch; 5 | }; 6 | type ContextConsumerProps = React.ConsumerProps & { 7 | selection: Selector; 8 | }; 9 | type ContextConsumer = React.FC> & { 10 | $$type: Symbol; 11 | }; 12 | type CustomContext = React.Context & { 13 | Consumer: ContextConsumer; 14 | }; 15 | type EqualityFn = (oldValue: T, newValue: T) => boolean; 16 | type ContextComparator = (oldValue: T, newValue: T) => 0; 17 | -------------------------------------------------------------------------------- /src/globals.ts: -------------------------------------------------------------------------------- 1 | const contextConsumerTypeSymbol = Symbol('ContextSelectionConsumer'); 2 | 3 | const contextListeners = new Map, Set>>(); 4 | 5 | export { contextListeners, contextConsumerTypeSymbol }; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as createContext } from './createContext'; 2 | export { default as useContextSelection } from './useContextSelection'; 3 | export { default as isEqualShallow } from './isEqualShallow'; 4 | -------------------------------------------------------------------------------- /src/isEqualShallow.test.ts: -------------------------------------------------------------------------------- 1 | import isEqualShallow from './isEqualShallow'; 2 | 3 | describe('isEqualShallow', () => { 4 | it('returns true if 2 objects are strictly identical', () => { 5 | const object = {}; 6 | const result = isEqualShallow(object, object); 7 | 8 | expect(result).toBeTruthy(); 9 | }); 10 | 11 | it('returns true if 2 objects are identical in shape', () => { 12 | const object1 = { 13 | a: 'A', 14 | b: 'B', 15 | }; 16 | const object2 = { 17 | a: 'A', 18 | b: 'B', 19 | }; 20 | const result = isEqualShallow(object1, object2); 21 | 22 | expect(result).toBeTruthy(); 23 | }); 24 | 25 | it('returns true if 2 primitive string values are identical', () => { 26 | const value1 = 'A'; 27 | const value2 = 'A'; 28 | const result = isEqualShallow(value1, value2); 29 | 30 | expect(result).toBeTruthy(); 31 | }); 32 | 33 | it('returns true if 2 primitive number values are identical', () => { 34 | const value1 = 1; 35 | const value2 = 1; 36 | const result = isEqualShallow(value1, value2); 37 | 38 | expect(result).toBeTruthy(); 39 | }); 40 | 41 | it('returns true if 2 primitive boolean values are identical', () => { 42 | const value1 = false; 43 | const value2 = false; 44 | const result = isEqualShallow(value1, value2); 45 | 46 | expect(result).toBeTruthy(); 47 | }); 48 | 49 | it('returns true if 2 primitive null values are identical', () => { 50 | const value1 = null; 51 | const value2 = null; 52 | const result = isEqualShallow(value1, value2); 53 | 54 | expect(result).toBeTruthy(); 55 | }); 56 | 57 | it('returns true if 2 primitive undefined values are identical', () => { 58 | const value1 = undefined; 59 | const value2 = undefined; 60 | const result = isEqualShallow(value1, value2); 61 | 62 | expect(result).toBeTruthy(); 63 | }); 64 | 65 | it('returns true if 2 function values are identical', () => { 66 | const fn = () => {}; 67 | const value1 = fn; 68 | const value2 = fn; 69 | const result = isEqualShallow(value1, value2); 70 | 71 | expect(result).toBeTruthy(); 72 | }); 73 | 74 | it('returns true if 2 function values are different', () => { 75 | const value1 = () => {}; 76 | const value2 = () => {}; 77 | const result = isEqualShallow(value1, value2); 78 | 79 | expect(result).toBeTruthy(); 80 | }); 81 | 82 | it('returns true if 2 objects are identical in shape and references', () => { 83 | const c = { c: 'C' }; 84 | const object1 = { 85 | a: 'A', 86 | b: 'B', 87 | c, 88 | }; 89 | const object2 = { 90 | a: 'A', 91 | b: 'B', 92 | c, 93 | }; 94 | const result = isEqualShallow(object1, object2); 95 | 96 | expect(result).toBeTruthy(); 97 | }); 98 | 99 | it('returns false if 2 objects are identical in shape but contain different references', () => { 100 | const object1 = { 101 | a: 'A', 102 | b: 'B', 103 | c: { c: 'C' }, 104 | }; 105 | const object2 = { 106 | a: 'A', 107 | b: 'B', 108 | c: { c: 'C' }, 109 | }; 110 | const result = isEqualShallow(object1, object2); 111 | 112 | expect(result).toBeFalsy(); 113 | }); 114 | 115 | it('returns false if 2 primitive string values are identical', () => { 116 | const value1 = 'A'; 117 | const value2 = 'B'; 118 | const result = isEqualShallow(value1, value2); 119 | 120 | expect(result).toBeFalsy(); 121 | }); 122 | 123 | it('returns false if 2 primitive number values are identical', () => { 124 | const value1 = 1; 125 | const value2 = 2; 126 | const result = isEqualShallow(value1, value2); 127 | 128 | expect(result).toBeFalsy(); 129 | }); 130 | 131 | it('returns false if 2 primitive boolean values are identical', () => { 132 | const value1 = false; 133 | const value2 = true; 134 | const result = isEqualShallow(value1, value2); 135 | 136 | expect(result).toBeFalsy(); 137 | }); 138 | 139 | it('returns false if 2 primitive null values are identical', () => { 140 | const value1 = null; 141 | const value2 = undefined; 142 | const result = isEqualShallow(value1, value2); 143 | 144 | expect(result).toBeFalsy(); 145 | }); 146 | 147 | it('returns false if 2 primitive undefined values are identical', () => { 148 | const value1 = undefined; 149 | const value2 = null; 150 | const result = isEqualShallow(value1, value2); 151 | 152 | expect(result).toBeFalsy(); 153 | }); 154 | 155 | it('ignores function values', () => { 156 | const object1 = { 157 | a: 'A', 158 | f: () => {}, 159 | }; 160 | const object2 = { 161 | a: 'A', 162 | f: () => {}, 163 | }; 164 | const result = isEqualShallow(object1, object2); 165 | 166 | expect(result).toBeTruthy(); 167 | }); 168 | 169 | it('compares array values', () => { 170 | const array1 = [1, 2, 3, 4]; 171 | const array2 = [1, 2, 3, 4]; 172 | const result = isEqualShallow(array1, array2); 173 | 174 | expect(result).toBeTruthy(); 175 | }); 176 | 177 | it('compares primitive number values', () => { 178 | const number1 = 10; 179 | const number2 = 10; 180 | const result = isEqualShallow(number1, number2); 181 | 182 | expect(result).toBeTruthy(); 183 | }); 184 | 185 | it('compares primitive string values', () => { 186 | const string1 = 'Hello world!'; 187 | const string2 = 'Hello world!'; 188 | const result = isEqualShallow(string1, string2); 189 | 190 | expect(result).toBeTruthy(); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /src/isEqualShallow.ts: -------------------------------------------------------------------------------- 1 | const hasOwn = {}.hasOwnProperty; 2 | 3 | function isEqualShallow(a: any, b: any): boolean { 4 | if (typeof a === 'function') { 5 | return true; 6 | } 7 | if (typeof a !== 'object') { 8 | return a === b; 9 | } 10 | 11 | if (a === b) return true; 12 | if (!a || !b) return false; 13 | 14 | const akeys = Object.keys(a); 15 | const bkeys = Object.keys(b); 16 | 17 | if (akeys.length !== bkeys.length) return false; 18 | 19 | let n = akeys.length; 20 | 21 | while (n--) { 22 | const k = akeys[n]; 23 | 24 | if (!hasOwn.call(b, k) || a[k] !== b[k]) { 25 | if (typeof a[k] === 'function' && typeof b[k] === 'function') { 26 | continue; 27 | } 28 | return false; 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | 35 | export default isEqualShallow; 36 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/useContextSelection.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderHook } from '@testing-library/react-hooks'; 3 | 4 | import useContextSelection from './useContextSelection'; 5 | import createContext from './createContext'; 6 | import { contextListeners } from './globals'; 7 | 8 | jest.mock('./globals', () => ({ 9 | contextListeners: new Map(), 10 | })); 11 | 12 | describe('createContextConsumer', () => { 13 | beforeEach(() => { 14 | jest.clearAllMocks(); 15 | contextListeners.clear(); 16 | }); 17 | 18 | it('throws an error if provided Context was not created using this library', () => { 19 | const Context = React.createContext({}); 20 | 21 | expect(() => useContextSelection(Context, () => {})).toThrow(); 22 | }); 23 | 24 | it('adds a listener to contextListeners Map', () => { 25 | const state = {}; 26 | createContext(state); 27 | 28 | expect(contextListeners?.size).toBe(1); 29 | }); 30 | 31 | it('returns selected state', () => { 32 | const state = { a: 'A', b: 'B' }; 33 | const Context = createContext(state); 34 | const selection = (state: any) => state.a; 35 | const { result } = renderHook(() => useContextSelection(Context, selection)); 36 | 37 | expect(result.current).toBe(state.a); 38 | }); 39 | 40 | it('does not call the returned selection if it is a function', () => { 41 | const Context = createContext({}); 42 | const spyFn = jest.fn(); 43 | const { result } = renderHook(() => useContextSelection(Context, () => spyFn)); 44 | 45 | expect(result.current).toBe(spyFn); 46 | expect(spyFn).not.toHaveBeenCalled(); 47 | }); 48 | 49 | it('adds listeners when executed useContextSelection on new mounted component', () => { 50 | const Context = createContext({}); 51 | renderHook(() => useContextSelection(Context, () => {})); 52 | renderHook(() => useContextSelection(Context, () => {})); 53 | 54 | expect(contextListeners.get(Context)?.size).toBe(2); 55 | }); 56 | 57 | it('removes listeners and whole Set after components unmounts', () => { 58 | const Context = createContext({}); 59 | const { unmount: unmount1 } = renderHook(() => useContextSelection(Context, () => {})); 60 | const { unmount: unmount2 } = renderHook(() => useContextSelection(Context, () => {})); 61 | 62 | unmount1(); 63 | unmount2(); 64 | 65 | expect(contextListeners.get(Context)?.size).toBe(0); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/useContextSelection.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { contextListeners, contextConsumerTypeSymbol } from './globals'; 4 | 5 | function useContextSelection(Context: React.Context, selection: Selector): any { 6 | if (process.env.NODE_ENV !== 'production') { 7 | // @ts-ignore 8 | if (Context.Consumer.$$type !== contextConsumerTypeSymbol) { 9 | throw new Error( 10 | `You need to provide a 'Context' instance created with 'createContext()' function from 'use-context-selection' library.` 11 | ); 12 | } 13 | } 14 | 15 | const contextValue = React.useContext(Context); 16 | const [currentSelection, forceUpdate] = React.useState(() => selection(contextValue)); 17 | const listener = React.useRef({ selection, forceUpdate }); 18 | 19 | React.useEffect(() => { 20 | const listeners = contextListeners.get(Context)!; 21 | 22 | listeners.add(listener.current); 23 | 24 | return () => { 25 | const listeners = contextListeners.get(Context)!; 26 | 27 | listeners.delete(listener.current); 28 | }; 29 | }, []); 30 | 31 | return currentSelection; 32 | } 33 | 34 | export default useContextSelection; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": [ 6 | "dom", 7 | "esnext" 8 | ], 9 | "moduleResolution": "node", 10 | "jsx": "react", 11 | "sourceMap": true, 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "allowSyntheticDefaultImports": true, 22 | "downlevelIteration": true, 23 | "target": "es5", 24 | "allowJs": true, 25 | "skipLibCheck": true, 26 | "strict": true, 27 | "forceConsistentCasingInFileNames": true, 28 | "resolveJsonModule": true, 29 | "isolatedModules": true, 30 | "noEmit": true 31 | }, 32 | "include": [ 33 | "src" 34 | ], 35 | "exclude": [ 36 | "node_modules", 37 | "dist", 38 | "example" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } --------------------------------------------------------------------------------