├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── pkg-pr.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── biome.json ├── docs └── Recipes.md ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src ├── InView.tsx ├── __tests__ │ ├── InView.test.tsx │ ├── browser.test.tsx │ ├── hooks.test.tsx │ ├── observe.test.ts │ └── setup.test.ts ├── index.tsx ├── observe.ts ├── test-utils.ts └── useInView.tsx ├── storybook ├── .storybook │ ├── main.ts │ ├── manager.ts │ ├── preview-head.html │ └── preview.ts ├── package.json ├── postcss.config.js ├── readme.md ├── stories │ ├── Hooks.story.tsx │ ├── InView.story.tsx │ ├── Intro.mdx │ ├── Recipes.mdx │ ├── elements.tsx │ └── story-utils.ts ├── tailwind.config.js └── tsconfig.json ├── tsconfig.json ├── tsup.config.ts └── vitest.config.mts /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .eslintignore text 60 | 61 | # misc config 62 | *.yaml text 63 | *.yml text 64 | .editorconfig text 65 | 66 | # build config 67 | *.npmignore text 68 | *.bowerrc text 69 | 70 | # Heroku 71 | Procfile text 72 | .slugignore text 73 | 74 | # Documentation 75 | *.md text 76 | LICENSE text 77 | AUTHORS text 78 | 79 | # Yarn 80 | yarn.lock text 81 | 82 | # 83 | ## These files are binary and should be left untouched 84 | # 85 | 86 | # (binary is a macro for -text -diff) 87 | *.png binary 88 | *.jpg binary 89 | *.jpeg binary 90 | *.gif binary 91 | *.ico binary 92 | *.mov binary 93 | *.mp4 binary 94 | *.mp3 binary 95 | *.flv binary 96 | *.fla binary 97 | *.swf binary 98 | *.gz binary 99 | *.zip binary 100 | *.7z binary 101 | *.ttf binary 102 | *.eot binary 103 | *.woff binary 104 | *.pyc binary 105 | *.pdf binary 106 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** Try and recreate the issue in a **Codesandbox**: 12 | 13 | - [Edit useInView](https://codesandbox.io/s/useinview-ud2vo?fontsize=14&hidenavigation=1&theme=dark) 14 | - [Edit InView render props](https://codesandbox.io/s/inview-render-props-hvhcb?fontsize=14&hidenavigation=1&theme=dark) 15 | - [Edit InView plain children](https://codesandbox.io/s/inview-plain-children-vv51y?fontsize=14&hidenavigation=1&theme=dark) 16 | 17 | **Expected behavior** A clear and concise description of what you expected to 18 | happen. 19 | 20 | **Screenshots** If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | 30 | - Device: [e.g. iPhone6] 31 | - OS: [e.g. iOS8.1] 32 | - Browser [e.g. stock browser, safari] 33 | - Version [e.g. 22] 34 | 35 | **Additional context** Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** A clear and 10 | concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** A clear and concise description of what you 13 | want to happen. 14 | 15 | **Describe alternatives you've considered** A clear and concise description of 16 | any alternative solutions or features you've considered. 17 | 18 | **Additional context** Add any other context or screenshots about the feature 19 | request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/pkg-pr.yml: -------------------------------------------------------------------------------- 1 | name: Publish Pull Requests 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | pr-package: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - run: npm install --global corepack@latest 9 | - run: corepack enable 10 | - uses: actions/checkout@v4 11 | - name: Setup Node.js 12 | uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | cache: "pnpm" 16 | - name: Install dependencies 17 | run: pnpm install 18 | - name: Build 19 | run: pnpm build 20 | - name: Publish preview package 21 | run: pnpx pkg-pr-new publish --no-template 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - run: npm install --global corepack@latest 11 | - run: corepack enable 12 | - uses: actions/checkout@v4 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | cache: "pnpm" 18 | - name: Install dependencies 19 | run: pnpm install 20 | - name: Install playwright 21 | run: pnpm exec playwright install 22 | - name: Lint 23 | run: pnpm biome ci . 24 | - name: Test 25 | run: pnpm vitest --coverage 26 | env: 27 | CI: true 28 | - name: Build 29 | run: pnpm build 30 | 31 | test_matrix: 32 | runs-on: ubuntu-latest 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | react: 37 | - 17 38 | - 18 39 | - 19 40 | - latest 41 | steps: 42 | - run: npm install --global corepack@latest 43 | - run: corepack enable 44 | - uses: actions/checkout@v4 45 | - name: Setup Node.js 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: 20 49 | cache: "pnpm" 50 | - name: Install dependencies 51 | run: pnpm install 52 | - name: Install legacy testing-library 53 | if: ${{ startsWith(matrix.react, '17') }} 54 | run: pnpm add -D @testing-library/react@12.1.4 55 | - name: Install React types 56 | run: pnpm add -D @types/react@${{ matrix.react }} @types/react-dom@${{ matrix.react }} 57 | - name: Install ${{ matrix.react }} 58 | run: pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }} 59 | - name: Validate types 60 | run: pnpm tsc 61 | - name: Run test 62 | run: | 63 | pnpm exec playwright install 64 | pnpm test 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .cache 3 | node_modules 4 | reports 5 | example 6 | lib/ 7 | dist/ 8 | test-utils/ 9 | coverage/ 10 | nuget 11 | npm-debug.log* 12 | .DS_store 13 | .eslintcache 14 | .idea 15 | .tern 16 | .tmp 17 | *.log 18 | storybook-static 19 | test-utils.js 20 | test-utils.d.ts 21 | __screenshots__ 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Mac OSX Files 2 | .DS_Store 3 | .Trashes 4 | .LSOverride 5 | 6 | # Node Modules 7 | node_modules 8 | npm-debug.log 9 | 10 | # General Files 11 | .sass-cache 12 | .hg 13 | .idea 14 | .svn 15 | .cache 16 | .project 17 | .tmp 18 | .eslintignore 19 | biome.json 20 | .flowconfig 21 | .editorconfig 22 | storybook/.storybook 23 | pnpm-lock.yaml 24 | 25 | # Project files 26 | coverage 27 | storybook/stories 28 | tests 29 | example 30 | jest-setup.js 31 | __tests__ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Welcome to `react-intersection-observer`! I'm thrilled that you're interested in 4 | contributing. Here are some guidelines to help you get started. 5 | 6 | The codebase is written in TypeScript, and split into two packages using PNPM 7 | workspaces: 8 | 9 | - `react-intersection-observer` - The main package, which contains the 10 | `useInView` hook and the `InView` component. 11 | - `storybook` - A Storybook project that is used to develop and test the 12 | `react-intersection-observer` package. 13 | 14 | ## Development 15 | 16 | Start by forking the repository, and after cloning it locally you can install 17 | the dependencies using [PNPM](https://pnpm.io/): 18 | 19 | ```shell 20 | pnpm install 21 | ``` 22 | 23 | Then you can start the Storybook development server with the `dev` task: 24 | 25 | ```shell 26 | pnpm dev 27 | ``` 28 | 29 | ## Semantic Versioning 30 | 31 | `react-intersection-observer` follows Semantic Versioning 2.0 as defined at 32 | http://semver.org. This means that releases will be numbered with the following 33 | format: 34 | 35 | `..` 36 | 37 | - Breaking changes and new features will increment the major version. 38 | - Backwards-compatible enhancements will increment the minor version. 39 | - Bug fixes and documentation changes will increment the patch version. 40 | 41 | ## Pull Request Process 42 | 43 | Fork the repository and create a branch for your feature/bug fix. 44 | 45 | - Add tests for your feature/bug fix. 46 | - Ensure that all tests pass before submitting your pull request. 47 | - Update the README.md file if necessary. 48 | - Ensure that your commits follow the conventions outlined in the next section. 49 | 50 | ### Commit Message Conventions 51 | 52 | - We use 53 | [semantic-release](https://github.com/semantic-release/semantic-release) to 54 | manage releases automatically. To ensure that releases are automatically 55 | versioned correctly, we follow the 56 | [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 57 | Conventions. This means that your commit messages should have the following 58 | format: 59 | 60 | `: ` 61 | 62 | Here's what each part of the commit message means: 63 | 64 | - ``: The type of change that you're committing. Valid types include 65 | `feat` for new features, `fix` for bug fixes, `docs` for documentation 66 | changes, and `chore` for changes that don't affect the code itself (e.g. 67 | updating dependencies). 68 | - ``: A short description of the change. 69 | 70 | ### Code Style and Linting 71 | 72 | `react-intersection-observer` uses [Biome](https://biomejs.dev/) for code 73 | formatting and linting. Please ensure that your changes are formatted with Biome before 74 | submitting your pull request. 75 | 76 | ### Testing 77 | 78 | `react-intersection-observer` uses [Vitest](https://vitest.dev/) for testing. 79 | Please ensure that your changes are covered by tests, and that all tests pass 80 | before submitting your pull request. 81 | 82 | You can run the tests with the `test` task: 83 | 84 | ```shell 85 | pnpm test 86 | ``` 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 React Intersection Observer authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-intersection-observer 2 | 3 | [![Version Badge][npm-version-svg]][package-url] 4 | [![GZipped size][npm-minzip-svg]][bundlephobia-url] 5 | [![Test][test-image]][test-url] 6 | [![License][license-image]][license-url] 7 | [![Downloads][downloads-image]][downloads-url] 8 | 9 | React implementation of the 10 | [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) 11 | to tell you when an element enters or leaves the viewport. Contains both a 12 | [Hooks](#useinview-hook), [render props](#render-props) and 13 | [plain children](#plain-children) implementation. 14 | 15 | ## Features 16 | 17 | - 🪝 **Hooks or Component API** - With `useInView` it's easier than ever to 18 | monitor elements 19 | - ⚡️ **Optimized performance** - Reuses Intersection Observer instances where 20 | possible 21 | - ⚙️ **Matches native API** - Intuitive to use 22 | - 🛠 **Written in TypeScript** - It'll fit right into your existing TypeScript 23 | project 24 | - 🧪 **Ready to test** - Mocks the Intersection Observer for easy testing with 25 | [Jest](https://jestjs.io/) or [Vitest](https://vitest.dev/) 26 | - 🌳 **Tree-shakeable** - Only include the parts you use 27 | - 💥 **Tiny bundle** - Around **~1.15kB** for `useInView` and **~1.6kB** for 28 | `` 29 | 30 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/thebuilder/react-intersection-observer) 31 | 32 | ## Installation 33 | 34 | Install the package with your package manager of choice: 35 | 36 | ```sh 37 | npm install react-intersection-observer --save 38 | ``` 39 | 40 | ## Usage 41 | 42 | ### `useInView` hook 43 | 44 | ```js 45 | // Use object destructuring, so you don't need to remember the exact order 46 | const { ref, inView, entry } = useInView(options); 47 | 48 | // Or array destructuring, making it easy to customize the field names 49 | const [ref, inView, entry] = useInView(options); 50 | ``` 51 | 52 | The `useInView` hook makes it easy to monitor the `inView` state of your 53 | components. Call the `useInView` hook with the (optional) [options](#options) 54 | you need. It will return an array containing a `ref`, the `inView` status and 55 | the current 56 | [`entry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). 57 | Assign the `ref` to the DOM element you want to monitor, and the hook will 58 | report the status. 59 | 60 | ```jsx 61 | import React from "react"; 62 | import { useInView } from "react-intersection-observer"; 63 | 64 | const Component = () => { 65 | const { ref, inView, entry } = useInView({ 66 | /* Optional options */ 67 | threshold: 0, 68 | }); 69 | 70 | return ( 71 |
72 |

{`Header inside viewport ${inView}.`}

73 |
74 | ); 75 | }; 76 | ``` 77 | 78 | ### Render props 79 | 80 | To use the `` component, you pass it a function. It will be called 81 | whenever the state changes, with the new value of `inView`. In addition to the 82 | `inView` prop, children also receive a `ref` that should be set on the 83 | containing DOM element. This is the element that the Intersection Observer will 84 | monitor. 85 | 86 | If you need it, you can also access the 87 | [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) 88 | on `entry`, giving you access to all the details about the current intersection 89 | state. 90 | 91 | ```jsx 92 | import { InView } from "react-intersection-observer"; 93 | 94 | const Component = () => ( 95 | 96 | {({ inView, ref, entry }) => ( 97 |
98 |

{`Header inside viewport ${inView}.`}

99 |
100 | )} 101 |
102 | ); 103 | 104 | export default Component; 105 | ``` 106 | 107 | ### Plain children 108 | 109 | You can pass any element to the ``, and it will handle creating the 110 | wrapping DOM element. Add a handler to the `onChange` method, and control the 111 | state in your own component. Any extra props you add to `` will be 112 | passed to the HTML element, allowing you set the `className`, `style`, etc. 113 | 114 | ```jsx 115 | import { InView } from "react-intersection-observer"; 116 | 117 | const Component = () => ( 118 | console.log("Inview:", inView)}> 119 |

Plain children are always rendered. Use onChange to monitor state.

120 |
121 | ); 122 | 123 | export default Component; 124 | ``` 125 | 126 | > [!NOTE] 127 | > When rendering a plain child, make sure you keep your HTML output 128 | > semantic. Change the `as` to match the context, and add a `className` to style 129 | > the ``. The component does not support Ref Forwarding, so if you 130 | > need a `ref` to the HTML element, use the Render Props version instead. 131 | 132 | ## API 133 | 134 | ### Options 135 | 136 | Provide these as the options argument in the `useInView` hook or as props on the 137 | **``** component. 138 | 139 | | Name | Type | Default | Description | 140 | | ---------------------- | ------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 141 | | **root** | `Element` | `document` | The Intersection Observer interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root is `null`, then the bounds of the actual document viewport are used. | 142 | | **rootMargin** | `string` | `'0px'` | Margin around the root. Can have values similar to the CSS margin property, e.g. `"10px 20px 30px 40px"` (top, right, bottom, left). Also supports percentages, to check if an element intersects with the center of the viewport for example `"-50% 0% -50% 0%"`. | 143 | | **threshold** | `number` or `number[]` | `0` | Number between `0` and `1` indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. | 144 | | **onChange** | `(inView, entry) => void` | `undefined` | Call this function whenever the in view state changes. It will receive the `inView` boolean, alongside the current `IntersectionObserverEntry`. | 145 | | **trackVisibility** 🧪 | `boolean` | `false` | A boolean indicating whether this Intersection Observer will track visibility changes on the target. | 146 | | **delay** 🧪 | `number` | `undefined` | A number indicating the minimum delay in milliseconds between notifications from this observer for a given target. This must be set to at least `100` if `trackVisibility` is `true`. | 147 | | **skip** | `boolean` | `false` | Skip creating the IntersectionObserver. You can use this to enable and disable the observer as needed. If `skip` is set while `inView`, the current state will still be kept. | 148 | | **triggerOnce** | `boolean` | `false` | Only trigger the observer once. | 149 | | **initialInView** | `boolean` | `false` | Set the initial value of the `inView` boolean. This can be used if you expect the element to be in the viewport to start with, and you want to trigger something when it leaves. | 150 | | **fallbackInView** | `boolean` | `undefined` | If the `IntersectionObserver` API isn't available in the client, the default behavior is to throw an Error. You can set a specific fallback behavior, and the `inView` value will be set to this instead of failing. To set a global default, you can set it with the `defaultFallbackInView()` | 151 | 152 | ### InView Props 153 | 154 | The **``** component also accepts the following props: 155 | 156 | | Name | Type | Default | Description | 157 | | ------------ | ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 158 | | **as** | `IntrinsicElement` | `'div'` | Render the wrapping element as this element. Defaults to `div`. If you want to use a custom component, please use the `useInView` hook or a render prop instead to manage the reference explictly. | 159 | | **children** | `({ref, inView, entry}) => ReactNode` or `ReactNode` | `undefined` | Children expects a function that receives an object containing the `inView` boolean and a `ref` that should be assigned to the element root. Alternatively pass a plain child, to have the `` deal with the wrapping element. You will also get the `IntersectionObserverEntry` as `entry`, giving you more details. | 160 | 161 | ### Intersection Observer v2 🧪 162 | 163 | The new 164 | [v2 implementation of IntersectionObserver](https://developers.google.com/web/updates/2019/02/intersectionobserver-v2) 165 | extends the original API, so you can track if the element is covered by another 166 | element or has filters applied to it. Useful for blocking clickjacking attempts 167 | or tracking ad exposure. 168 | 169 | To use it, you'll need to add the new `trackVisibility` and `delay` options. 170 | When you get the `entry` back, you can then monitor if `isVisible` is `true`. 171 | 172 | ```jsx 173 | const TrackVisible = () => { 174 | const { ref, entry } = useInView({ trackVisibility: true, delay: 100 }); 175 | return
{entry?.isVisible}
; 176 | }; 177 | ``` 178 | 179 | This is still a very new addition, so check 180 | [caniuse](https://caniuse.com/#feat=intersectionobserver-v2) for current browser 181 | support. If `trackVisibility` has been set, and the current browser doesn't 182 | support it, a fallback has been added to always report `isVisible` as `true`. 183 | 184 | It's not added to the TypeScript `lib.d.ts` file yet, so you will also have to 185 | extend the `IntersectionObserverEntry` with the `isVisible` boolean. 186 | 187 | ## Recipes 188 | 189 | The `IntersectionObserver` itself is just a simple but powerful tool. Here's a 190 | few ideas for how you can use it. 191 | 192 | - [Lazy image load](docs/Recipes.md#lazy-image-load) 193 | - [Trigger animations](docs/Recipes.md#trigger-animations) 194 | - [Track impressions](docs/Recipes.md#track-impressions) _(Google Analytics, Tag 195 | Manager, etc.)_ 196 | 197 | ## FAQ 198 | 199 | ### How can I assign multiple refs to a component? 200 | 201 | You can wrap multiple `ref` assignments in a single `useCallback`: 202 | 203 | ```jsx 204 | import React, { useRef, useCallback } from "react"; 205 | import { useInView } from "react-intersection-observer"; 206 | 207 | function Component(props) { 208 | const ref = useRef(); 209 | const { ref: inViewRef, inView } = useInView(); 210 | 211 | // Use `useCallback` so we don't recreate the function on each render 212 | const setRefs = useCallback( 213 | (node) => { 214 | // Ref's from useRef needs to have the node assigned to `current` 215 | ref.current = node; 216 | // Callback refs, like the one from `useInView`, is a function that takes the node as an argument 217 | inViewRef(node); 218 | }, 219 | [inViewRef], 220 | ); 221 | 222 | return
Shared ref is visible: {inView}
; 223 | } 224 | ``` 225 | 226 | ### `rootMargin` isn't working as expected 227 | 228 | When using `rootMargin`, the margin gets added to the current `root` - If your 229 | application is running inside a `