├── .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 | You need to enable JavaScript to run this app.
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 | simulateClicks('context', setButtonsDisabled)}>{ buttonsDisabled ? 'Processing...' : 'Simulate Click All' }
66 |
67 |
68 |
69 |
70 | {(new Array(numOfRows).fill(0).map((_, index) => (
71 |
72 | )))}
73 |
74 |
75 |
76 |
77 |
78 |
79 | useContextSelection
80 | simulateClicks('use-context-selection', setButtonsDisabled)}>{ buttonsDisabled ? 'Processing...' : 'Simulate Click All' }
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 | Update value
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 |
32 | You need to enable JavaScript to run this app.
33 |
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 | Increment
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 | Increment
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 | }
--------------------------------------------------------------------------------