├── .all-contributorsrc
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── validate.yml
├── .gitignore
├── .huskyrc.js
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── other
├── CODE_OF_CONDUCT.md
├── MAINTAINING.md
├── USERS.md
└── manual-releases.md
├── package.json
├── src
├── __tests__
│ └── index.ts
└── index.ts
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "use-deep-compare-effect",
3 | "projectOwner": "kentcdodds",
4 | "imageSize": 100,
5 | "commit": false,
6 | "contributorsPerLine": 7,
7 | "repoHost": "https://github.com",
8 | "repoType": "github",
9 | "skipCi": false,
10 | "files": [
11 | "README.md"
12 | ],
13 | "contributors": [
14 | {
15 | "login": "kentcdodds",
16 | "name": "Kent C. Dodds",
17 | "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3",
18 | "profile": "https://kentcdodds.com",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "infra",
23 | "test"
24 | ]
25 | },
26 | {
27 | "login": "edygar",
28 | "name": "Edygar de Lima Oliveira",
29 | "avatar_url": "https://avatars2.githubusercontent.com/u/566280?v=4",
30 | "profile": "https://linkedin.com/in/edygar/en",
31 | "contributions": [
32 | "code",
33 | "test"
34 | ]
35 | },
36 | {
37 | "login": "jdorfman",
38 | "name": "Justin Dorfman",
39 | "avatar_url": "https://avatars1.githubusercontent.com/u/398230?v=4",
40 | "profile": "https://stackshare.io/jdorfman/decisions",
41 | "contributions": [
42 | "fundingFinding"
43 | ]
44 | },
45 | {
46 | "login": "antonhalim",
47 | "name": "Anton Halim",
48 | "avatar_url": "https://avatars1.githubusercontent.com/u/10498035?v=4",
49 | "profile": "https://antonhalim.com",
50 | "contributions": [
51 | "doc"
52 | ]
53 | },
54 | {
55 | "login": "MichaelDeBoey",
56 | "name": "Michaël De Boey",
57 | "avatar_url": "https://avatars3.githubusercontent.com/u/6643991?v=4",
58 | "profile": "https://michaeldeboey.be",
59 | "contributions": [
60 | "code"
61 | ]
62 | },
63 | {
64 | "login": "tobiasbueschel",
65 | "name": "Tobias Büschel",
66 | "avatar_url": "https://avatars3.githubusercontent.com/u/13087421?v=4",
67 | "profile": "http://linkedin.com/in/tbueschel",
68 | "contributions": [
69 | "doc"
70 | ]
71 | },
72 | {
73 | "login": "Aprillion",
74 | "name": "Peter Hozák",
75 | "avatar_url": "https://avatars0.githubusercontent.com/u/1087670?v=4",
76 | "profile": "http://peter.hozak.info/",
77 | "contributions": [
78 | "review"
79 | ]
80 | },
81 | {
82 | "login": "rbusquet",
83 | "name": "Ricardo Busquet",
84 | "avatar_url": "https://avatars1.githubusercontent.com/u/7198302?v=4",
85 | "profile": "https://ricardobusquet.com",
86 | "contributions": [
87 | "review"
88 | ]
89 | },
90 | {
91 | "login": "daveisfera",
92 | "name": "Dave Johansen",
93 | "avatar_url": "https://avatars3.githubusercontent.com/u/1686193?v=4",
94 | "profile": "https://github.com/daveisfera",
95 | "contributions": [
96 | "bug"
97 | ]
98 | },
99 | {
100 | "login": "samuel-knutson",
101 | "name": "Sam Knutson",
102 | "avatar_url": "https://avatars0.githubusercontent.com/u/2458585?v=4",
103 | "profile": "https://github.com/samuel-knutson",
104 | "contributions": [
105 | "doc"
106 | ]
107 | },
108 | {
109 | "login": "AlbertLucianto",
110 | "name": "Albert Lucianto",
111 | "avatar_url": "https://avatars0.githubusercontent.com/u/23165866?v=4",
112 | "profile": "https://albertlucianto.github.io",
113 | "contributions": [
114 | "bug",
115 | "code",
116 | "test"
117 | ]
118 | },
119 | {
120 | "login": "jasperck",
121 | "name": "Jasper Chang",
122 | "avatar_url": "https://avatars.githubusercontent.com/u/5002476?v=4",
123 | "profile": "https://github.com/jasperck",
124 | "contributions": [
125 | "doc"
126 | ]
127 | },
128 | {
129 | "login": "cvolant",
130 | "name": "cvolant",
131 | "avatar_url": "https://avatars.githubusercontent.com/u/37238472?v=4",
132 | "profile": "https://github.com/cvolant",
133 | "contributions": [
134 | "code"
135 | ]
136 | },
137 | {
138 | "login": "phwebi",
139 | "name": "Phoebe Gao",
140 | "avatar_url": "https://avatars.githubusercontent.com/u/2031802?v=4",
141 | "profile": "https://github.com/phwebi",
142 | "contributions": [
143 | "code"
144 | ]
145 | },
146 | {
147 | "login": "ablakey",
148 | "name": "Andrew Blakey",
149 | "avatar_url": "https://avatars.githubusercontent.com/u/9021944?v=4",
150 | "profile": "https://github.com/ablakey",
151 | "contributions": [
152 | "doc"
153 | ]
154 | },
155 | {
156 | "login": "mkarots",
157 | "name": "@mkarots",
158 | "avatar_url": "https://avatars.githubusercontent.com/u/7574557?v=4",
159 | "profile": "https://github.com/mkarots",
160 | "contributions": [
161 | "doc"
162 | ]
163 | }
164 | ]
165 | }
166 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | - `use-deep-compare-effect` version:
15 | - `node` version:
16 | - `npm` version:
17 |
18 | Relevant code or config
19 |
20 | ```javascript
21 |
22 | ```
23 |
24 | What you did:
25 |
26 | What happened:
27 |
28 |
29 |
30 | Reproduction repository:
31 |
32 |
36 |
37 | Problem description:
38 |
39 | Suggested solution:
40 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | **What**:
20 |
21 |
22 |
23 | **Why**:
24 |
25 |
26 |
27 | **How**:
28 |
29 |
30 |
31 | **Checklist**:
32 |
33 |
34 |
35 |
36 | - [ ] Documentation
37 | - [ ] Tests
38 | - [ ] Ready to be merged
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/validate.yml:
--------------------------------------------------------------------------------
1 | name: validate
2 | on:
3 | push:
4 | branches:
5 | - '+([0-9])?(.{+([0-9]),x}).x'
6 | - 'main'
7 | - 'next'
8 | - 'next-major'
9 | - 'beta'
10 | - 'alpha'
11 | - '!all-contributors/**'
12 | pull_request: {}
13 | jobs:
14 | main:
15 | # ignore all-contributors PRs
16 | if: ${{ !contains(github.head_ref, 'all-contributors') }}
17 | strategy:
18 | matrix:
19 | node: [10.13, 12, 14, 15]
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: 🛑 Cancel Previous Runs
23 | uses: styfle/cancel-workflow-action@0.6.0
24 | with:
25 | access_token: ${{ secrets.GITHUB_TOKEN }}
26 |
27 | - name: ⬇️ Checkout repo
28 | uses: actions/checkout@v2
29 |
30 | - name: ⎔ Setup node
31 | uses: actions/setup-node@v1
32 | with:
33 | node-version: ${{ matrix.node }}
34 |
35 | - name: 📥 Download deps
36 | uses: bahmutov/npm-install@v1
37 | with:
38 | useLockFile: false
39 |
40 | - name: ▶️ Run validate script
41 | run: npm run validate
42 |
43 | - name: ⬆️ Upload coverage report
44 | uses: codecov/codecov-action@v1
45 |
46 | release:
47 | needs: main
48 | runs-on: ubuntu-latest
49 | if:
50 | ${{ github.repository == 'kentcdodds/use-deep-compare-effect' &&
51 | contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha',
52 | github.ref) && github.event_name == 'push' }}
53 | steps:
54 | - name: 🛑 Cancel Previous Runs
55 | uses: styfle/cancel-workflow-action@0.6.0
56 | with:
57 | access_token: ${{ secrets.GITHUB_TOKEN }}
58 |
59 | - name: ⬇️ Checkout repo
60 | uses: actions/checkout@v2
61 |
62 | - name: ⎔ Setup node
63 | uses: actions/setup-node@v1
64 | with:
65 | node-version: 14
66 |
67 | - name: 📥 Download deps
68 | uses: bahmutov/npm-install@v1
69 | with:
70 | useLockFile: false
71 |
72 | - name: 🏗 Run build script
73 | run: npm run build
74 |
75 | - name: 🚀 Release
76 | uses: cycjimmy/semantic-release-action@v2
77 | with:
78 | semantic_version: 17
79 | branches: |
80 | [
81 | '+([0-9])?(.{+([0-9]),x}).x',
82 | 'main',
83 | 'next',
84 | 'next-major',
85 | {name: 'beta', prerelease: true},
86 | {name: 'alpha', prerelease: true}
87 | ]
88 | env:
89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
91 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 | .DS_Store
5 |
6 | # these cause more harm than good
7 | # when working with contributors
8 | package-lock.json
9 | yarn.lock
10 |
--------------------------------------------------------------------------------
/.huskyrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('kcd-scripts/husky')
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('kcd-scripts/prettier')
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | The changelog is automatically updated using
4 | [semantic-release](https://github.com/semantic-release/semantic-release). You
5 | can see it on the [releases page](../../releases).
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | me+coc@kentcdodds.com. All complaints will be reviewed and investigated promptly
64 | and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for being willing to contribute!
4 |
5 | **Working on your first Pull Request?** You can learn how from this _free_
6 | series [How to Contribute to an Open Source Project on GitHub][egghead]
7 |
8 | ## Project setup
9 |
10 | 1. Fork and clone the repo
11 | 2. Run `npm run setup -s` to install dependencies and run validation
12 | 3. Create a branch for your PR with `git checkout -b pr/your-branch-name`
13 |
14 | > Tip: Keep your `master` branch pointing at the original repository and make
15 | > pull requests from branches on your fork. To do this, run:
16 | >
17 | > ```
18 | > git remote add upstream https://github.com/kentcdodds/use-deep-compare-effect
19 | > git fetch upstream
20 | > git branch --set-upstream-to=upstream/master master
21 | > ```
22 | >
23 | > This will add the original repository as a "remote" called "upstream," Then
24 | > fetch the git information from that remote, then set your local `master`
25 | > branch to use the upstream master branch whenever you run `git pull`. Then you
26 | > can make all of your pull request branches based on this `master` branch.
27 | > Whenever you want to update your version of `master`, do a regular `git pull`.
28 |
29 | ## Committing and Pushing changes
30 |
31 | Please make sure to run the tests before you commit your changes. You can run
32 | `npm run test:update` which will update any snapshots that need updating. Make
33 | sure to include those changes (if they exist) in your commit.
34 |
35 | ## Help needed
36 |
37 | Please checkout the [the open issues][issues]
38 |
39 | Also, please watch the repo and respond to questions/bug reports/feature
40 | requests! Thanks!
41 |
42 |
43 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
44 | [issues]: https://github.com/kentcdodds/use-deep-compare-effect/issues
45 |
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2020 Kent C. Dodds
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
use-deep-compare-effect 🐋
3 |
4 |
It's React's useEffect hook, except using deep comparison on the inputs, not
5 | reference equality
6 |
7 |
8 | ---
9 |
10 |
11 | [![Build Status][build-badge]][build]
12 | [![Code Coverage][coverage-badge]][coverage]
13 | [![version][version-badge]][package]
14 | [![downloads][downloads-badge]][npmtrends]
15 | [![MIT License][license-badge]][license]
16 | [![All Contributors][all-contributors-badge]](#contributors-)
17 | [![PRs Welcome][prs-badge]][prs]
18 | [![Code of Conduct][coc-badge]][coc]
19 |
20 |
21 | > WARNING: Please _only_ use this if you really can't find a way to use
22 | > `React.useEffect`. There's often a better way to do what you're trying to do
23 | > than a deep comparison.
24 |
25 | ## The Problem
26 |
27 | React's built-in [`useEffect`][react-hooks] hook has a second argument called
28 | the "dependencies array" and it allows you to optimize when React will call your
29 | effect callback. React will do a comparison between each of the values (via
30 | [`Object.is`][object-is]) to determine whether your effect callback should be
31 | called.
32 |
33 | The problem is that if you need to provide an object for one of those
34 | dependencies and that object is new every render, then even if none of the
35 | properties changed, your effect will get called anyway.
36 |
37 | ## Table of Contents
38 |
39 |
40 |
41 |
42 | - [Installation](#installation)
43 | - [Usage](#usage)
44 | - [Other Solutions](#other-solutions)
45 | - [Issues](#issues)
46 | - [🐛 Bugs](#-bugs)
47 | - [💡 Feature Requests](#-feature-requests)
48 | - [Contributors ✨](#contributors-)
49 | - [LICENSE](#license)
50 |
51 |
52 |
53 | ## Installation
54 |
55 | This module is distributed via [npm][npm] which is bundled with [node][node] and
56 | should be installed as one of your project's dependencies:
57 |
58 | ```shell
59 | npm install --save use-deep-compare-effect
60 | ```
61 |
62 | ## Usage
63 |
64 | You use it in place of `React.useEffect`.
65 |
66 | > NOTE: Only use this if your values are objects or arrays that contain objects.
67 | > Otherwise, you should just use `React.useEffect`. In case of "polymorphic"
68 | > values (eg: sometimes object, sometimes a boolean), use
69 | > `useDeepCompareEffectNoCheck`, but do it at your own risk, as maybe there can
70 | > be better approaches to the problem.
71 |
72 | > NOTE: Be careful when your dependency is an object which contains a function.
73 | > If that function is defined on the object during a render, then it's changed
74 | > and the effect callback will be called every render.
75 | > [Issue](https://github.com/kentcdodds/use-deep-compare-effect/issues/47) has more context.
76 |
77 | Example:
78 |
79 | ```jsx
80 | import React from 'react'
81 | import ReactDOM from 'react-dom'
82 | import useDeepCompareEffect from 'use-deep-compare-effect'
83 |
84 | function Query({query, variables}) {
85 | // some code...
86 |
87 | useDeepCompareEffect(
88 | () => {
89 | // make an HTTP request or whatever with the query and variables
90 | // optionally return a cleanup function if necessary
91 | },
92 | // query is a string, but variables is an object. With the way Query is used
93 | // in the example above, `variables` will be a new object every render.
94 | // useDeepCompareEffect will do a deep comparison and your callback is only
95 | // run when the variables object actually has changes.
96 | [query, variables],
97 | )
98 |
99 | return {/* awesome UI here */}
100 | }
101 | ```
102 |
103 | ## Other Solutions
104 |
105 | [use-custom-compare-effect](https://github.com/sanjagh/use-custom-compare-effect)
106 |
107 | ## Issues
108 |
109 | _Looking to contribute? Look for the [Good First Issue][good-first-issue]
110 | label._
111 |
112 | ### 🐛 Bugs
113 |
114 | Please file an issue for bugs, missing documentation, or unexpected behavior.
115 |
116 | [**See Bugs**][bugs]
117 |
118 | ### 💡 Feature Requests
119 |
120 | Please file an issue to suggest new features. Vote on feature requests by adding
121 | a 👍. This helps maintainers prioritize what to work on.
122 |
123 | [**See Feature Requests**][requests]
124 |
125 | ## Contributors ✨
126 |
127 | Thanks goes to these people ([emoji key][emojis]):
128 |
129 |
130 |
131 |
132 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | This project follows the [all-contributors][all-contributors] specification.
163 | Contributions of any kind welcome!
164 |
165 | ## LICENSE
166 |
167 | MIT
168 |
169 |
170 | [npm]: https://www.npmjs.com
171 | [node]: https://nodejs.org
172 | [build-badge]: https://img.shields.io/github/workflow/status/kentcdodds/use-deep-compare-effect/validate?logo=github&style=flat-square
173 | [build]: https://github.com/kentcdodds/use-deep-compare-effect/actions?query=workflow%3Avalidate
174 | [coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/use-deep-compare-effect.svg?style=flat-square
175 | [coverage]: https://codecov.io/github/kentcdodds/use-deep-compare-effect
176 | [version-badge]: https://img.shields.io/npm/v/use-deep-compare-effect.svg?style=flat-square
177 | [package]: https://www.npmjs.com/package/use-deep-compare-effect
178 | [downloads-badge]: https://img.shields.io/npm/dm/use-deep-compare-effect.svg?style=flat-square
179 | [npmtrends]: http://www.npmtrends.com/use-deep-compare-effect
180 | [license-badge]: https://img.shields.io/npm/l/use-deep-compare-effect.svg?style=flat-square
181 | [license]: https://github.com/kentcdodds/use-deep-compare-effect/blob/master/LICENSE
182 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
183 | [prs]: http://makeapullrequest.com
184 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
185 | [coc]: https://github.com/kentcdodds/use-deep-compare-effect/blob/master/CODE_OF_CONDUCT.md
186 | [emojis]: https://github.com/all-contributors/all-contributors#emoji-key
187 | [all-contributors]: https://github.com/all-contributors/all-contributors
188 | [all-contributors-badge]: https://img.shields.io/github/all-contributors/kentcdodds/use-deep-compare-effect?color=orange&style=flat-square
189 | [bugs]: https://github.com/kentcdodds/use-deep-compare-effect/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
190 | [requests]: https://github.com/kentcdodds/use-deep-compare-effect/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
191 | [good-first-issue]: https://github.com/kentcdodds/use-deep-compare-effect/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement+label%3A%22good+first+issue%22
192 | [react-hooks]: https://reactjs.org/docs/hooks-effect.html
193 | [object-is]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
194 |
195 |
--------------------------------------------------------------------------------
/other/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of
9 | experience, nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or reject
41 | comments, commits, code, wiki edits, issues, and other contributions that are
42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any
43 | contributor for other behaviors that they deem inappropriate, threatening,
44 | offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at kent+coc@doddsfamily.us. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an
62 | incident. Further details of specific enforcement policies may be posted
63 | separately.
64 |
65 | Project maintainers who do not follow or enforce the Code of Conduct in good
66 | faith may face temporary or permanent repercussions as determined by other
67 | members of the project's leadership.
68 |
69 | ## Attribution
70 |
71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
72 | version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
73 |
74 | [homepage]: http://contributor-covenant.org
75 | [version]: http://contributor-covenant.org/version/1/4/
76 |
--------------------------------------------------------------------------------
/other/MAINTAINING.md:
--------------------------------------------------------------------------------
1 | # Maintaining
2 |
3 | This is documentation for maintainers of this project.
4 |
5 | ## Code of Conduct
6 |
7 | Please review, understand, and be an example of it. Violations of the code of
8 | conduct are taken seriously, even (especially) for maintainers.
9 |
10 | ## Issues
11 |
12 | We want to support and build the community. We do that best by helping people
13 | learn to solve their own problems. We have an issue template and hopefully most
14 | folks follow it. If it's not clear what the issue is, invite them to create a
15 | minimal reproduction of what they're trying to accomplish or the bug they think
16 | they've found.
17 |
18 | Once it's determined that a code change is necessary, point people to
19 | [makeapullrequest.com](http://makeapullrequest.com) and invite them to make a
20 | pull request. If they're the one who needs the feature, they're the one who can
21 | build it. If they need some hand holding and you have time to lend a hand,
22 | please do so. It's an investment into another human being, and an investment
23 | into a potential maintainer.
24 |
25 | Remember that this is open source, so the code is not yours, it's ours. If
26 | someone needs a change in the codebase, you don't have to make it happen
27 | yourself. Commit as much time to the project as you want/need to. Nobody can ask
28 | any more of you than that.
29 |
30 | ## Pull Requests
31 |
32 | As a maintainer, you're fine to make your branches on the main repo or on your
33 | own fork. Either way is fine.
34 |
35 | When we receive a pull request, a github action is kicked off automatically (see
36 | the `.github/workflows/validate.yml` for what runs in the action). We avoid
37 | merging anything that breaks the validate action.
38 |
39 | Please review PRs and focus on the code rather than the individual. You never
40 | know when this is someone's first ever PR and we want their experience to be as
41 | positive as possible, so be uplifting and constructive.
42 |
43 | When you merge the pull request, 99% of the time you should use the
44 | [Squash and merge](https://help.github.com/articles/merging-a-pull-request/)
45 | feature. This keeps our git history clean, but more importantly, this allows us
46 | to make any necessary changes to the commit message so we release what we want
47 | to release. See the next section on Releases for more about that.
48 |
49 | ## Release
50 |
51 | Our releases are automatic. They happen whenever code lands into `master`. A
52 | github action gets kicked off and if it's successful, a tool called
53 | [`semantic-release`](https://github.com/semantic-release/semantic-release) is
54 | used to automatically publish a new release to npm as well as a changelog to
55 | GitHub. It is only able to determine the version and whether a release is
56 | necessary by the git commit messages. With this in mind, **please brush up on
57 | [the commit message convention][commit] which drives our releases.**
58 |
59 | > One important note about this: Please make sure that commit messages do NOT
60 | > contain the words "BREAKING CHANGE" in them unless we want to push a major
61 | > version. I've been burned by this more than once where someone will include
62 | > "BREAKING CHANGE: None" and it will end up releasing a new major version. Not
63 | > a huge deal honestly, but kind of annoying...
64 |
65 | ## Thanks!
66 |
67 | Thank you so much for helping to maintain this project!
68 |
69 | [commit]:
70 | https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md
71 |
--------------------------------------------------------------------------------
/other/USERS.md:
--------------------------------------------------------------------------------
1 | # Users
2 |
3 | If you or your company uses this project, add your name to this list! Eventually
4 | we may have a website to showcase these (wanna build it!?)
5 |
6 | > No users have been added yet!
7 |
8 |
13 |
--------------------------------------------------------------------------------
/other/manual-releases.md:
--------------------------------------------------------------------------------
1 | # manual-releases
2 |
3 | This project has an automated release set up. So things are only released when
4 | there are useful changes in the code that justify a release. But sometimes
5 | things get messed up one way or another and we need to trigger the release
6 | ourselves. When this happens, simply bump the number below and commit that with
7 | the following commit message based on your needs:
8 |
9 | **Major**
10 |
11 | ```
12 | fix(release): manually release a major version
13 |
14 | There was an issue with a major release, so this manual-releases.md
15 | change is to release a new major version.
16 |
17 | Reference: #
18 |
19 | BREAKING CHANGE:
20 | ```
21 |
22 | **Minor**
23 |
24 | ```
25 | feat(release): manually release a minor version
26 |
27 | There was an issue with a minor release, so this manual-releases.md
28 | change is to release a new minor version.
29 |
30 | Reference: #
31 | ```
32 |
33 | **Patch**
34 |
35 | ```
36 | fix(release): manually release a patch version
37 |
38 | There was an issue with a patch release, so this manual-releases.md
39 | change is to release a new patch version.
40 |
41 | Reference: #
42 | ```
43 |
44 | The number of times we've had to do a manual release is: 1
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-deep-compare-effect",
3 | "version": "0.0.0-semantically-released",
4 | "description": "It's react's useEffect hook, except using deep comparison on the inputs, not reference equality",
5 | "main": "dist/use-deep-compare-effect.cjs.js",
6 | "module": "dist/use-deep-compare-effect.esm.js",
7 | "types": "dist/index.d.ts",
8 | "engines": {
9 | "node": ">=10",
10 | "npm": ">=6"
11 | },
12 | "scripts": {
13 | "build": "kcd-scripts build --bundle",
14 | "lint": "kcd-scripts lint",
15 | "setup": "npm install && npm run validate -s",
16 | "test": "kcd-scripts test",
17 | "test:update": "npm test -- --updateSnapshot --coverage",
18 | "typecheck": "kcd-scripts typecheck",
19 | "validate": "kcd-scripts validate"
20 | },
21 | "files": [
22 | "dist"
23 | ],
24 | "keywords": [],
25 | "author": "Kent C. Dodds (https://kentcdodds.com)",
26 | "license": "MIT",
27 | "peerDependencies": {
28 | "react": ">=16.13"
29 | },
30 | "dependencies": {
31 | "@babel/runtime": "^7.12.5",
32 | "dequal": "^2.0.2"
33 | },
34 | "devDependencies": {
35 | "@testing-library/react-hooks": "^3.4.2",
36 | "@types/react": "^17.0.0",
37 | "jest-in-case": "^1.0.2",
38 | "kcd-scripts": "^7.2.1",
39 | "react": "^17.0.1",
40 | "react-dom": "^17.0.1",
41 | "react-test-renderer": "^17.0.1",
42 | "typescript": "^4.1.2"
43 | },
44 | "eslintConfig": {
45 | "extends": "./node_modules/kcd-scripts/eslint.js"
46 | },
47 | "eslintIgnore": [
48 | "node_modules",
49 | "coverage",
50 | "dist"
51 | ],
52 | "repository": {
53 | "type": "git",
54 | "url": "https://github.com/kentcdodds/use-deep-compare-effect"
55 | },
56 | "bugs": {
57 | "url": "https://github.com/kentcdodds/use-deep-compare-effect/issues"
58 | },
59 | "homepage": "https://github.com/kentcdodds/use-deep-compare-effect#readme"
60 | }
61 |
--------------------------------------------------------------------------------
/src/__tests__/index.ts:
--------------------------------------------------------------------------------
1 | import {useState} from 'react'
2 | import {renderHook} from '@testing-library/react-hooks'
3 | import useDeepCompareEffect, {useDeepCompareEffectNoCheck} from '../'
4 |
5 | test('useDeepCompareEffect throws an error if using it with an empty array', () => {
6 | const {result} = renderHook(() => useDeepCompareEffect(() => {}, []))
7 | expect(result.error).toMatchInlineSnapshot(
8 | `[Error: useDeepCompareEffect should not be used with no dependencies. Use React.useEffect instead.]`,
9 | )
10 | })
11 |
12 | test('useDeepCompareEffect throws an error if using it with an array of only primitive values', () => {
13 | const {result} = renderHook(() =>
14 | useDeepCompareEffect(() => {}, [true, 1, 'string']),
15 | )
16 | expect(result.error).toMatchInlineSnapshot(
17 | `[Error: useDeepCompareEffect should not be used with dependencies that are all primitive values. Use React.useEffect instead.]`,
18 | )
19 | })
20 |
21 | test("useDeepCompareEffectNoCheck don't throw an error if using it with an array of only primitive values", () => {
22 | const errorMock = jest.spyOn(console, 'error').mockImplementation(() => {})
23 | expect(() =>
24 | renderHook(() =>
25 | useDeepCompareEffectNoCheck(() => {}, [true, 1, 'string']),
26 | ),
27 | ).not.toThrow()
28 | expect(console.error).toHaveBeenCalledTimes(0)
29 | errorMock.mockRestore()
30 | })
31 |
32 | test('in production mode there are no errors thrown', () => {
33 | const env = process.env.NODE_ENV
34 | process.env.NODE_ENV = 'production'
35 | renderHook(() => useDeepCompareEffect(() => {}, [true, 1, 'string']))
36 | renderHook(() => useDeepCompareEffect(() => {}, []))
37 | process.env.NODE_ENV = env
38 | })
39 |
40 | test('useDeepCompareEffect handles changing values as expected', () => {
41 | const callback = jest.fn()
42 | let deps = [1, {a: 'b'}, true]
43 | const {rerender} = renderHook(() => useDeepCompareEffect(callback, deps))
44 |
45 | expect(callback).toHaveBeenCalledTimes(1)
46 | callback.mockClear()
47 |
48 | // no change
49 | rerender()
50 | expect(callback).toHaveBeenCalledTimes(0)
51 | callback.mockClear()
52 |
53 | // no-change (new object with same properties)
54 | deps = [1, {a: 'b'}, true]
55 | rerender()
56 | expect(callback).toHaveBeenCalledTimes(0)
57 | callback.mockClear()
58 |
59 | // change (new primitive value)
60 | deps = [2, {a: 'b'}, true]
61 | rerender()
62 | expect(callback).toHaveBeenCalledTimes(1)
63 | callback.mockClear()
64 |
65 | // no-change
66 | rerender()
67 | expect(callback).toHaveBeenCalledTimes(0)
68 | callback.mockClear()
69 |
70 | // change (new primitive value)
71 | deps = [1, {a: 'b'}, false]
72 | rerender()
73 | expect(callback).toHaveBeenCalledTimes(1)
74 | callback.mockClear()
75 |
76 | // change (new properties on object)
77 | deps = [1, {a: 'c'}, false]
78 | rerender()
79 | expect(callback).toHaveBeenCalledTimes(1)
80 | callback.mockClear()
81 | })
82 |
83 | // this may be useful in the future, but we don't support it today so I thought
84 | // it'd be good to include as a test as it would be a breaking change if we
85 | // did add support. I'm inclined to not support this. Manipulation is not good.
86 | test('useDeepCompareEffect does NOT work with manipulation', () => {
87 | const callback = jest.fn()
88 | const deps = [{a: 'b'}]
89 | const {rerender} = renderHook(() => useDeepCompareEffect(callback, deps))
90 | expect(callback).toHaveBeenCalledTimes(1)
91 | callback.mockClear()
92 |
93 | deps[0].a = 'c'
94 | rerender()
95 | expect(callback).toHaveBeenCalledTimes(0)
96 | })
97 |
98 | test('useDeepCompareEffect works with deep object similarities/differences', () => {
99 | const callback = jest.fn()
100 | let deps: Array> = [{a: {b: {c: 'd'}}}]
101 | const {rerender} = renderHook(() => useDeepCompareEffect(callback, deps))
102 | expect(callback).toHaveBeenCalledTimes(1)
103 | callback.mockClear()
104 |
105 | // change primitive value
106 | deps = [{a: {b: {c: 'e'}}}]
107 | rerender()
108 | expect(callback).toHaveBeenCalledTimes(1)
109 | callback.mockClear()
110 |
111 | // no-change
112 | deps = [{a: {b: {c: 'e'}}}]
113 | rerender()
114 | expect(callback).toHaveBeenCalledTimes(0)
115 | callback.mockClear()
116 |
117 | // add property
118 | deps = [{a: {b: {c: 'e'}, f: 'g'}}]
119 | rerender()
120 | expect(callback).toHaveBeenCalledTimes(1)
121 | callback.mockClear()
122 | })
123 |
124 | test('useDeepCompareEffect works with getDerivedStateFromProps', () => {
125 | const callback = jest.fn()
126 | const {rerender} = renderHook(
127 | ({a}: {a: number}) => {
128 | const [lastA, setLastA] = useState(a)
129 | const [c, setC] = useState(5)
130 | if (lastA !== a) {
131 | setLastA(a)
132 | setC(1)
133 | }
134 | useDeepCompareEffect(callback, [{a, c}])
135 | },
136 | {initialProps: {a: 1}},
137 | )
138 | expect(callback).toHaveBeenCalledTimes(1)
139 | callback.mockClear()
140 |
141 | // change a, and reset c
142 | rerender({a: 2})
143 | expect(callback).toHaveBeenCalledTimes(1)
144 | callback.mockClear()
145 | })
146 |
147 | /* eslint no-console:0 */
148 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {dequal as deepEqual} from 'dequal'
3 |
4 | type UseEffectParams = Parameters
5 | type EffectCallback = UseEffectParams[0]
6 | type DependencyList = UseEffectParams[1]
7 | // yes, I know it's void, but I like what this communicates about
8 | // the intent of these functions: It's just like useEffect
9 | type UseEffectReturn = ReturnType
10 |
11 | function checkDeps(deps: DependencyList) {
12 | if (!deps || !deps.length) {
13 | throw new Error(
14 | 'useDeepCompareEffect should not be used with no dependencies. Use React.useEffect instead.',
15 | )
16 | }
17 | if (deps.every(isPrimitive)) {
18 | throw new Error(
19 | 'useDeepCompareEffect should not be used with dependencies that are all primitive values. Use React.useEffect instead.',
20 | )
21 | }
22 | }
23 |
24 | function isPrimitive(val: unknown) {
25 | return val == null || /^[sbn]/.test(typeof val)
26 | }
27 |
28 | /**
29 | * @param value the value to be memoized (usually a dependency list)
30 | * @returns a memoized version of the value as long as it remains deeply equal
31 | */
32 | export function useDeepCompareMemoize(value: T) {
33 | const ref = React.useRef(value)
34 | const signalRef = React.useRef(0)
35 |
36 | if (!deepEqual(value, ref.current)) {
37 | ref.current = value
38 | signalRef.current += 1
39 | }
40 |
41 | // eslint-disable-next-line react-hooks/exhaustive-deps
42 | return React.useMemo(() => ref.current, [signalRef.current])
43 | }
44 |
45 | function useDeepCompareEffect(
46 | callback: EffectCallback,
47 | dependencies: DependencyList,
48 | ): UseEffectReturn {
49 | if (process.env.NODE_ENV !== 'production') {
50 | checkDeps(dependencies)
51 | }
52 | // eslint-disable-next-line react-hooks/exhaustive-deps
53 | return React.useEffect(callback, useDeepCompareMemoize(dependencies))
54 | }
55 |
56 | export function useDeepCompareEffectNoCheck(
57 | callback: EffectCallback,
58 | dependencies: DependencyList,
59 | ): UseEffectReturn {
60 | // eslint-disable-next-line react-hooks/exhaustive-deps
61 | return React.useEffect(callback, useDeepCompareMemoize(dependencies))
62 | }
63 |
64 | export default useDeepCompareEffect
65 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/kcd-scripts/shared-tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------