├── .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 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 |

Kent C. Dodds

💻 📖 🚇 ⚠️

Edygar de Lima Oliveira

💻 ⚠️

Justin Dorfman

🔍

Anton Halim

📖

Michaël De Boey

💻

Tobias Büschel

📖

Peter Hozák

👀

Ricardo Busquet

👀

Dave Johansen

🐛

Sam Knutson

📖

Albert Lucianto

🐛 💻 ⚠️

Jasper Chang

📖

cvolant

💻

Phoebe Gao

💻

Andrew Blakey

📖

@mkarots

📖
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 | --------------------------------------------------------------------------------